From 612bfdd41c77934c9a3bc60cc602db2a962f621c Mon Sep 17 00:00:00 2001 From: "Steven G. Johnson" Date: Mon, 12 Dec 2016 22:59:16 -0500 Subject: [PATCH] docs for new broadcasting dot-operator behavior --- NEWS.md | 15 ++++++- doc/src/manual/arrays.md | 37 +++++++++------- doc/src/manual/functions.md | 11 +++-- doc/src/manual/mathematical-operations.md | 53 ++++++++++++++++------- doc/src/manual/performance-tips.md | 47 +++++++++++++++++++- doc/src/stdlib/punctuation.md | 2 +- 6 files changed, 125 insertions(+), 40 deletions(-) diff --git a/NEWS.md b/NEWS.md index 0b16422ee98c4..3531d937eea30 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,7 +7,7 @@ New language features Language changes ---------------- - * Multiline and singleline nonstandard command literals have been added. A + * Multi-line and single-line nonstandard command literals have been added. A nonstandard command literal is like a nonstandard string literal, but the syntax uses backquotes (``` ` ```) instead of double quotes, and the resulting macro called is suffixed with `_cmd`. For instance, the syntax @@ -17,6 +17,11 @@ Language changes module. For instance, `Base.r"x"` is now parsed as `Base.@r_str "x"`. Previously, this syntax parsed as an implicit multiplication. ([#18690]) + * For every binary operator `⨳`, `a .⨳ b` is now automatically equivalent to + the `broadcast` call `(⨳).(a, b)`. Hence, one no longer defines methods + for `.*` etcetera. This also means that "dot operations" automatically + fuse into a single loop, along with other dot calls `f.(x)`. ([#17623]) + Breaking changes ---------------- @@ -34,6 +39,14 @@ This section lists changes that do not have deprecation warnings. * `broadcast` now handles tuples, and treats any argument that is not a tuple or an array as a "scalar" ([#16986]). + * `broadcast` now produces a `BitArray` instead of `Array{Bool}` for + functions yielding a boolean result. If you want `Array{Bool}`, use + `broadcast!` or `.=` ([#17623]). + + * Operations like `.+` and `.*` on `Range` objects are now generic + `broadcast` calls (see above) and produce an `Array`. If you want + a `Range` result, use `+` and `*`, etcetera ([#17623]). + Library improvements -------------------- diff --git a/doc/src/manual/arrays.md b/doc/src/manual/arrays.md index 972d50a1ffbbc..ee8f9a62a6bea 100644 --- a/doc/src/manual/arrays.md +++ b/doc/src/manual/arrays.md @@ -402,29 +402,33 @@ specify this trait, the default value `LinearSlow()` is used. ### Vectorized Operators and Functions -The following operators are supported for arrays. The dot version of a binary operator should -be used for elementwise operations. +The following operators are supported for arrays. Also, *every* binary +operator supports a [dot version](@ref man-dot-operators) that can be +applied to arrays (and combinations of arrays and scalars) as a +[fused broadcasting operation](@ref man-vectorized). (For comparison +operations like `<`, *only* the `.<` version is applicable to arrays.) 1. Unary arithmetic -- `-`, `+`, `!` -2. Binary arithmetic -- `+`, `-`, `*`, `.*`, `/`, `./`, `\`, `.\`, `^`, `.^`, `div`, `mod` -3. Comparison -- `.==`, `.!=`, `.<`, `.<=`, `.>`, `.>=` +2. Binary arithmetic -- `+`, `-`, `*`, `/`, `\`, `^`, `.^`, `div`, `mod` +3. Comparison -- `==`, `!=`, `≈` ([`isapprox`](@ref)), `≉` 4. Unary Boolean or bitwise -- `~` 5. Binary Boolean or bitwise -- `&`, `|`, `$` -Some operators without dots operate elementwise anyway when one argument is a scalar. These operators -are `*`, `+`, `-`, and the bitwise operators. The operators `/` and `\` operate elementwise when +Some operators without dots operate elementwise anyway when one argument is a scalar: +`*`, `+`, `-`, and the bitwise operators. The operators `/` and `\` operate elementwise when the denominator is a scalar. Note that comparisons such as `==` operate on whole arrays, giving a single boolean answer. Use -dot operators for elementwise comparisons. +dot operators like `.==` for elementwise comparisons. -To enable convenient vectorization of mathematical and other operations, Julia provides the compact -syntax `f.(args...)`, e.g. `sin.(x)` or `min.(x,y)`, for elementwise operations over arrays or -mixtures of arrays and scalars (a [`broadcast()`](@ref) operation). See [Dot Syntax for Vectorizing Functions](@ref). +To enable convenient vectorization of mathematical and other operations, Julia [provides the compact +syntax](@ref man-vectorized) `f.(args...)`, e.g. `sin.(x)` or `min.(x,y)`, for elementwise operations over arrays or mixtures of arrays and scalars (a [Broadcasting](@ref) operation); these +have the additional advantage of "fusing" into a single loop when combined with +dot operators and other dot calls. Note that there is a difference between `max.(a,b)`, which `broadcast`s [`max()`](@ref) elementwise -over `a` and `b`, and `maximum(a)`, which finds the largest value within `a`. The same statements -hold for `min.(a,b)` and `minimum(a)`. +over `a` and `b`, and `maximum(a)`, which finds the largest value within `a`. The same relationship +holds for `min.(a,b)` and `minimum(a)`. ### Broadcasting @@ -461,11 +465,14 @@ julia> broadcast(+, a, b) 1.73659 0.873631 ``` -Elementwise operators such as `.+` and `.*` perform broadcasting if necessary. There is also a -[`broadcast!()`](@ref) function to specify an explicit destination, and [`broadcast_getindex()`](@ref) +[Dotted operators](@ref man-dot-operators) such as `.+` and `.*` are equivalent +to `broadcast` calls (except that they fuse, as described below). There is also a +[`broadcast!()`](@ref) function to specify an explicit destination (which can also +be accessed in a fusing fashion by `.=` assignment), and functions [`broadcast_getindex()`](@ref) and [`broadcast_setindex!()`](@ref) that broadcast the indices before indexing. Moreover, `f.(args...)` is equivalent to `broadcast(f, args...)`, providing a convenient syntax to broadcast any function -([Dot Syntax for Vectorizing Functions](@ref)). +([dot syntax](@ref man-vectorized)), with the added benefit that nested "dot calls" +*fuse* into a single `broadcast` loop. Additionally, [`broadcast()`](@ref) is not limited to arrays (see the function documentation), it also handles tuples and treats any argument that is not an array or a tuple as a "scalar". diff --git a/doc/src/manual/functions.md b/doc/src/manual/functions.md index 921a316d5b010..f2cf0a82c4c27 100644 --- a/doc/src/manual/functions.md +++ b/doc/src/manual/functions.md @@ -554,7 +554,7 @@ normally or threw an exception. (The `try/finally` construct will be described i With the `do` block syntax, it helps to check the documentation or implementation to know how the arguments of the user function are initialized. -## Dot Syntax for Vectorizing Functions +## [Dot Syntax for Vectorizing Functions](@id man-vectorized) In technical-computing languages, it is common to have "vectorized" versions of functions, which simply apply a given function `f(x)` to each element of an array `A` to yield a new array via @@ -595,10 +595,10 @@ overwriting `X` with `sin.(Y)` in-place. If the left-hand side is an array-index e.g. `X[2:end] .= sin.(Y)`, then it translates to `broadcast!` on a `view`, e.g. `broadcast!(sin, view(X, 2:endof(X)), Y)`, so that the left-hand side is updated in-place. -(In future versions of Julia, operators like `.*` will also be handled with the same mechanism: -they will be equivalent to `broadcast` calls and will be fused with other nested "dot" calls. - `X .+= Y` is equivalent to `X .= X .+ Y` and will eventually result in a fused in-place assignment. -Similarly for `.*=` etcetera.) +Operators like `.*` are handled with the same mechanism: +they are equivalent to `broadcast` calls and are fused with other nested "dot" calls. + `X .+= Y` etcetera is equivalent to `X .= X .+ Y` and results in a fused in-place assignment; + see also [dot operators](@ref man-dot-operators). ## Further Reading @@ -607,4 +607,3 @@ a sophisticated type system and allows multiple dispatch on argument types. None given here provide any type annotations on their arguments, meaning that they are applicable to all types of arguments. The type system is described in [Types](@ref man-types) and defining a function in terms of methods chosen by multiple dispatch on run-time argument types is described in [Methods](@ref). - diff --git a/doc/src/manual/mathematical-operations.md b/doc/src/manual/mathematical-operations.md index 608da6f15eb6c..80f78149dfe4c 100644 --- a/doc/src/manual/mathematical-operations.md +++ b/doc/src/manual/mathematical-operations.md @@ -127,6 +127,34 @@ The updating versions of all the binary arithmetic and bitwise operators are: true ``` +## [Elementwise "dot" operators](@id man-dot-operators) + +For *every* binary operation like `^`, there is a corresponding +"dot" operation `.^` that is *automatically* defined +to perform `^` element-by-element on arrays. For example, +`[1,2,3] ^ 3` is not defined, since there is no standard +mathematical meaning to "cubing" an array, but `[1,2,3] .^ 3` +is defined as being equivalent to the elementwise operation `[1^3, 2^3, 3^3]`. + +More specifically, `a .^ b` is parsed as the [vectorized call](@ref man-vectorized) +`(^).(a,b)`, which performs a [broadcast](@ref Broadcasting) operation: +it can combine arrays and scalars, arrays of the same size (performing +the operation elementwise), and even arrays of different shapes (e.g. +combining row and column vectors to produce a matrix). Moreover, like +all vectorized calls via the "dot syntax," these "dot operators" are +*fusing*. For example, if you compute `2 .* A.^2 .+ sin.(A)` for an +array `A`, it performs a *single* loop over `A`, computing `2a^2 + sin(a)` +for each element of `A`. + +Furthermore, "dotted" updating operators like `a .+= b` are parsed +as `a .= a .+ b`, where `.=` is a fused *in-place* assignment operation +(see the [dot syntax documentation](@ref man-vectorized)). + +Note the dot syntax is also applicable to user-defined operators. +For example, if you define `⊗(A,B) = kron(A,B)` to give a convenient +infix syntax `A ⊗ B` for Kronecker products ([`kron`](@ref)), then +`[A,B] .⊗ [C,D]` will compute `[A⊗C, B⊗D]` with no additional coding. + ## Numeric Comparisons Standard comparison operations are defined for all the primitive numeric types: @@ -265,13 +293,6 @@ Chaining comparisons is often quite convenient in numerical code. Chained compar which allows them to work on arrays. For example, `0 .< A .< 1` gives a boolean array whose entries are true where the corresponding elements of `A` are between 0 and 1. -The operator [`.<`](@ref) is intended for array objects; the operation `A .< B` is valid only -if `A` and `B` have the same dimensions. The operator returns an array with boolean entries and -with the same dimensions as `A` and `B`. Such operators are called *elementwise*; Julia offers -a suite of elementwise operators: [`.*`](@ref), [`.+`](@ref), etc. Some of the elementwise operators -can take a scalar operand such as the example `0 .< A .< 1` in the preceding paragraph. This notation -means that the scalar operand should be replicated for each entry of the array. - Note the evaluation behavior of chained comparisons: ```julia @@ -303,15 +324,15 @@ Julia applies the following order of operations, from highest precedence to lowe | Category | Operators | |:-------------- |:------------------------------------------------------------------------------------------------- | | Syntax | `.` followed by `::` | -| Exponentiation | `^` and its elementwise equivalent `.^` | -| Fractions | `//` and `.//` | -| Multiplication | `* / % & \` and `.* ./ .% .\` | -| Bitshifts | `<< >> >>>` and `.<< .>> .>>>` | -| Addition | `+ - \| ⊻` and `.+ .-` | +| Exponentiation | `^` | +| Fractions | `//` | +| Multiplication | `* / % & \` | +| Bitshifts | `<< >> >>>` | +| Addition | `+ - \| ⊻` | | Syntax | `: ..` followed by `\|>` | -| Comparisons | `> < >= <= == === != !== <:` and `.> .< .>= .<= .== .!=` | +| Comparisons | `> < >= <= == === != !== <:` | | Control flow | `&&` followed by `\|\|` followed by `?` | -| Assignments | `= += -= *= /= //= \= ^= ÷= %= \|= &= ⊻= <<= >>= >>>=` and `.+= .-= .*= ./= .//= .\= .^= .÷= .%=` | +| Assignments | `= += -= *= /= //= \= ^= ÷= %= \|= &= ⊻= <<= >>= >>>=` | ### Elementary Functions @@ -321,8 +342,8 @@ including integers, floating-point numbers, rationals, and complexes, wherever s make sense. Moreover, these functions (like any Julia function) can be applied in "vectorized" fashion to -arrays and other collections with the syntax `f.(A)`, e.g. `sin.(A)` will compute the elementwise -sine of each element of an array `A`. See [Dot Syntax for Vectorizing Functions](@ref). +arrays and other collections with the [dot syntax](@ref man-vectorized) `f.(A)`, +e.g. `sin.(A)` will compute the elementwise sine of each element of an array `A`. ## Numerical Conversions diff --git a/doc/src/manual/performance-tips.md b/doc/src/manual/performance-tips.md index 05417b9d48fea..75adec530f037 100644 --- a/doc/src/manual/performance-tips.md +++ b/doc/src/manual/performance-tips.md @@ -863,7 +863,52 @@ type from an algorithm. In the example above, we could have passed a `SubArray` Taken to its extreme, pre-allocation can make your code uglier, so performance measurements and some judgment may be required. However, for "vectorized" (element-wise) functions, the convenient syntax `x .= f.(y)` can be used for in-place operations with fused loops and no temporary arrays -([Dot Syntax for Vectorizing Functions](@ref)). +(see the [dot syntax for vectorizing functions](@ref man-vectorized)). + +## More dots: Fuse vectorized operations + +Julia has a special [dot syntax](@ref man-vectorized) that converts +any scalar function into a "vectorized" function call, and any operator +into a "vectorized" operator, with the special property that nested +"dot calls" are *fusing*: they are combined at the syntax level into +a single loop, without allocating temporary arrays. If you use `.=` and +similar assignment operators, the result can also be stored in-place +in a pre-allocated array (see above). + +In a linear-algebra context, this means that even though operations like +`vector + vector` and `vector * scalar` are defined, it can be advantageous +to instead use `vector .+ vector` and `vector .* scalar` because the +resulting loops can be fused with surrounding computations. For example, +consider the two functions: + +```julia +f(x) = 3 * x.^2 + 4 * x + 7 * x.^3 +fdot(x) = 3 .* x.^2 .+ 4 .* x .+ 7 .* x.^3 +``` + +Both `f` and `fdot` compute the same thing. However, `fdot` is +significantly faster when applied to an array: + +```julia +julia> x = rand(10^6); + +julia> @time f(x); + 0.020244 seconds (26 allocations: 53.407 MB, 35.88% gc time) + +julia> @time fdot(x); + 0.004579 seconds (10 allocations: 7.630 MB) + +julia> @time f.(x); + 0.004391 seconds (35 allocations: 7.631 MB) +``` + +That is, `fdot(x)` is more than four times faster and allocates 1/7 the +memory of `f(x)`, because each `*` and `+` operation in `f(x)` allocates +a new temporary array and executes in a separate loop. (Of course, +if you just do `f.(x)` then it is as fast as `fdot(x)` in this +example, but in many contexts it is more convenient to just sprinkle +some dots in your expressions rather than defining a separate function +for each vectorized operation.) ## Avoid string interpolation for I/O diff --git a/doc/src/stdlib/punctuation.md b/doc/src/stdlib/punctuation.md index 1a94ca6e66b35..7b237b70d767c 100644 --- a/doc/src/stdlib/punctuation.md +++ b/doc/src/stdlib/punctuation.md @@ -35,7 +35,7 @@ Extended documentation for mathematical symbols & functions is [here](@ref math- | `''` | delimit character literals | | ``` ` ` ``` | delimit external process (command) specifications | | `...` | splice arguments into a function call or declare a varargs function or type | -| `.` | access named fields in objects or names inside modules, also prefixes elementwise operators | +| `.` | access named fields in objects/modules, also prefixes elementwise operator/function calls | | `a:b` | range a, a+1, a+2, ..., b | | `a:s:b` | range a, a+s, a+2s, ..., b | | `:` | index an entire dimension (1:end) |