Skip to content

Commit 9648a4b

Browse files
authored
docs: fix some typos (#572)
Signed-off-by: John E <jeis4wpi@outlook.com>
1 parent 06749be commit 9648a4b

4 files changed

Lines changed: 12 additions & 18 deletions

File tree

β€ŽREADME.mdβ€Ž

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
[![LoopVectorization Downloads](https://shields.io/endpoint?url=https://pkgs.genieframework.com/api/v1/badge/LoopVectorization)](https://pkgs.genieframework.com?packages=LoopVectorization)
1414

1515

16-
## Maintanence
16+
## Maintenance
1717
The plan is for `LoopVectorization.jl` to be maintained through the [SciML Small Grants](https://sciml.ai/small_grants/#update_loopvectorization_to_support_changes_in_julia_v112_200) program.
1818
If you would like to see an issue fixed, or support extended to another Julia patch release, please consider:
1919
1. Donating to the SciML Small Grants program, with a note on the purpose of your donation.
@@ -58,7 +58,7 @@ For simple loops like a dot product, LoopVectorization.jl's most important optim
5858
<details>
5959
<summaryClick me! ></summary>
6060
<p>
61-
61+
6262
```julia
6363
julia> using LoopVectorization, BenchmarkTools
6464

@@ -314,7 +314,7 @@ f = KwargCall(round, (digits = 3,));
314314
<p>
315315

316316
The key to the `@turbo` macro's performance gains is leveraging knowledge of exactly how data like `Float64`s and `Int`s are handled by a CPU. As such, it is not strightforward to generalize the `@turbo` macro to work on arrays containing structs such as `Matrix{Complex{Float64}}`. Instead, it is currently recommended that users wishing to apply `@turbo` to arrays of structs use packages such as [StructArrays.jl](https://github.com/JuliaArrays/StructArrays.jl) which transform an array where each element is a struct into a struct where each element is an array. Using StructArrays.jl, we can write a matrix multiply (gemm) kernel that works on matrices of `Complex{Float64}`s and `Complex{Int}`s:
317-
```julia
317+
```julia
318318
using LoopVectorization, LinearAlgebra, StructArrays, BenchmarkTools, Test
319319

320320
BLAS.set_num_threads(1); @show BLAS.vendor()
@@ -373,7 +373,7 @@ julia> @test C1 β‰ˆ C2
373373
Test Passed
374374
```
375375

376-
Similar approaches can be taken to make kernels working with a variety of numeric struct types such as [dual numbers](https://github.com/JuliaDiff/DualNumbers.jl), [DoubleFloats](https://github.com/JuliaMath/DoubleFloats.jl), etc.
376+
Similar approaches can be taken to make kernels working with a variety of numeric struct types such as [dual numbers](https://github.com/JuliaDiff/DualNumbers.jl), [DoubleFloats](https://github.com/JuliaMath/DoubleFloats.jl), etc.
377377

378378
</p>
379379
</details>

β€Žbenchmark/driver.jlβ€Ž

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -285,7 +285,7 @@ println("Benchmark results for unrolled 3x3 convolution:");
285285
filter2d_unrolled_bench = benchmark_filter2dunrolled(sizes);
286286
println(filter2d_unrolled_bench);
287287

288-
println("Benchmark resutls of summing squared error:");
288+
println("Benchmark results of summing squared error:");
289289
sse_bench = benchmark_sse(sizes);
290290
println(sse_bench);
291291
println("Benchmark results of exponentiating a vector:");

β€Ždocs/src/examples/dot_product.mdβ€Ž

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ function jdotavx(a, b)
1111
s
1212
end
1313
```
14-
To execute the loop using SIMD (Single Instruction Multiple Data) instructions, you have to unroll the loop. Rather than evaluating the loop as written -- adding element-wise products to a single accumulator one after the other -- you can multiply short vectors loaded from `a` and `b` and add their results to a vector of accumulators.
14+
To execute the loop using SIMD (Single Instruction Multiple Data) instructions, you have to unroll the loop. Rather than evaluating the loop as written -- adding element-wise products to a single accumulator one after the other -- you can multiply short vectors loaded from `a` and `b` and add their results to a vector of accumulators.
1515

1616
Most modern CPUs found in laptops or desktops have the AVX instruction set, which allows them to operate on 256 bit vectors -- meaning the vectors can hold 4 double precision (64 bit) floats. Some have the AVX512 instruction set, which increases the vector size to 512 bits, and also adds many new instructions that make vectorizing easier. To be general across CPUs and data types, I'll refer to the number of elements in the vectors with `W`. I'll also refer to unrolling a loop by a factor of `W` and loading vectors from it as "vectorizing" that loop.
1717

@@ -21,20 +21,20 @@ This means that if we used a single vector to accumulate a product, we'd only ge
2121
If we had 8 accumulators, then theoretically we could perform two per clock cycle, and after the 4th cycle, our first operations are done so that we can reuse them.
2222

2323
However, there is another bottle neck: we can only perform 2 aligned loads per clock cycle (or 1 unaligned load). [Alignment here means with respect to a memory address boundary, if your vectors are 256 bits, then a load/store is aligned if it is with respect to a memory address that is an integer multiple of 32 bytes (256 bits = 32 bytes).]
24-
Thus, in 4 clock cycles, we can do up to 8 loads. But each `fma` requires 2 loads, meaning we are limited to 4 of them per 4 clock cyles, and any unrolling beyond 4 gives us no benefit.
24+
Thus, in 4 clock cycles, we can do up to 8 loads. But each `fma` requires 2 loads, meaning we are limited to 4 of them per 4 clock cycles, and any unrolling beyond 4 gives us no benefit.
2525

2626
Double precision benchmarks pitting Julia's builtin dot product, and code compiled with a variety of compilers:
2727
![dot](https://raw.githubusercontent.com/JuliaSIMD/LoopVectorization.jl/docsassets/docs/src/assets/bench_dot_v2.svg)
2828
What we just described is the core of the approach used by all these compilers. The variation in results is explained mostly by how they handle vectors with lengths that are not an integer multiple of `W`. I ran these on a computer with AVX512 so that `W = 8`. LLVM, the backend compiler of both Julia and Clang, shows rapid performance degradation as `N % 4W` increases, where `N` is the length of the vectors.
29-
This is because, to handle the remainder, it uses a scalar loop that runs as written: multiply and add single elements, one after the other.
29+
This is because, to handle the remainder, it uses a scalar loop that runs as written: multiply and add single elements, one after the other.
3030

3131
Initially, GCC (gfortran) stumbled in throughput, because it does not use separate accumulation vectors by default except on Power, even with `-funroll-loops`.
3232
I compiled with the flags `-fvariable-expansion-in-unroller --param max-variable-expansions-in-unroller=4` to allow for 4 accumulation vectors, yielding good performance.
3333

3434
The Intel compilers have a secondary vectorized loop without any additional unrolling that masks off excess lanes beyond `N` (for when `N` isn't an integer multiple of `W`).
3535
LoopVectorization uses `if/ifelse` checks to determine how many extra vectors are needed, the last of which is masked.
3636

37-
Neither GCC nor LLVM use masks (without LoopVectorization's assitance).
37+
Neither GCC nor LLVM use masks (without LoopVectorization's assistance).
3838

3939
I am not certain, but I believe Intel and GCC check for the vector's alignment, and align them if necessary. Julia guarantees that the start of arrays beyond a certain size are aligned, so this is not an optimization I have implemented. But it may be worthwhile for handling large matrices with a number of rows that isn't an integer multiple of `W`. For such matrices, the first column may be aligned, but the next will not be.
4040

@@ -56,5 +56,3 @@ For this reason, LoopVectorization now unrolls by 8 -- it decides how much to un
5656
This algorithm may need refinement, because Julia (without LoopVectorization) only unrolls by 4, yet achieves roughly the same performance as LoopVectorization at multiples of `4W = 32`, although performance declines rapidly from there due to the slow scalar loop. Performance for most is much higher -- more GFLOPS -- than the normal dot product, but still under half of the CPU's potential 131.2 GFLOPS, suggesting that some other bottlenecks are preventing the core from attaining 2 fmas per clock cycle.
5757
Note also that `8W = 64`, so we don't really have enough iterations of the loop to amortize the overhead of performing the reductions of all these vectors into a single scalar.
5858
By the time the vectors are long enough to do this, we'll start running into memory bandwidth bottlenecks.
59-
60-

β€Ždocs/src/examples/matrix_multiplication.mdβ€Ž

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ function A_mul_B!(C, A, B)
1515
end
1616
end
1717
```
18-
and this can handle all transposed/not-tranposed permutations. LoopVectorization will change loop orders and strategy as appropriate based on the types of the input matrices. For each of the others, I wrote separate functions to handle each case.
18+
and this can handle all transposed/not-transposed permutations. LoopVectorization will change loop orders and strategy as appropriate based on the types of the input matrices. For each of the others, I wrote separate functions to handle each case.
1919
Letting all three matrices be square and `Size` x `Size`, we attain the following benchmark results:
2020

2121
![AmulB](https://raw.githubusercontent.com/JuliaSIMD/LoopVectorization.jl/docsassets/docs/src/assets/bench_AmulB_v2.svg)
@@ -24,18 +24,14 @@ This is classic GEMM, `𝐂 = 𝐀 * 𝐁`. GFortran's intrinsic `matmul` functi
2424
![AmulBt](https://github.com/JuliaSIMD/LoopVectorization.jl/raw/docsassets/docs/src/assets/bench_AmulBt_v2.svg)
2525
The optimal pattern for `𝐂 = 𝐀 * 𝐁ᡀ` is almost identical to that for `𝐂 = 𝐀 * 𝐁`. Yet, gfortran's `matmul` intrinsic stumbles, surprisingly doing much worse than gfortran + loops, and almost certainly worse than allocating memory for `𝐁ᡀ` and creating the explicit copy.
2626

27-
ifort did equally well whethor or not `𝐁` was transposed, while LoopVectorization's performance degraded slightly faster as a function of size in the transposed case, because strides between memory accesses are larger when `𝐁` is transposed. But it still performed best of all the compiled loops over this size range, losing out to MKL and eventually OpenBLAS.
27+
ifort did equally well whether or not `𝐁` was transposed, while LoopVectorization's performance degraded slightly faster as a function of size in the transposed case, because strides between memory accesses are larger when `𝐁` is transposed. But it still performed best of all the compiled loops over this size range, losing out to MKL and eventually OpenBLAS.
2828
icc interestingly does better when it is transposed.
2929

30-
GEMM is easiest when the matrix `𝐀` is not tranposed (assuming column-major memory layouts), because then you can sum up columns of `𝐀` to store into `𝐂`. If `𝐀` were transposed, then we cannot efficiently load contiguous elements from `𝐀` that can best stored directly in `𝐂`. So for `𝐂 = 𝐀ᡀ * 𝐁`, contiguous vectors along the `k`-loop have to be reduced, adding some overhead.
30+
GEMM is easiest when the matrix `𝐀` is not transposed (assuming column-major memory layouts), because then you can sum up columns of `𝐀` to store into `𝐂`. If `𝐀` were transposed, then we cannot efficiently load contiguous elements from `𝐀` that can best stored directly in `𝐂`. So for `𝐂 = 𝐀ᡀ * 𝐁`, contiguous vectors along the `k`-loop have to be reduced, adding some overhead.
3131
![AtmulB](https://github.com/JuliaSIMD/LoopVectorization.jl/raw/docsassets/docs/src/assets/bench_AtmulB_v2.svg)
3232
Packing is critical for performance here. LoopVectorization does not pack, therefore it is well behind MKL and OpenBLAS, which do. Eigen packs, but is poorly optimized for this CPU architecture.
3333

3434
When both `𝐀` and ` 𝐁` are transposed, we now have `𝐂 = 𝐀ᡀ * 𝐁ᡀ = (𝐁 * 𝐀)α΅€`.
3535
![AtmulBt](https://github.com/JuliaSIMD/LoopVectorization.jl/raw/docsassets/docs/src/assets/bench_AtmulBt_v2.svg)
3636
Julia, Clang, and gfortran all struggled to vectorize this, because none of the matrices share a contiguous access: `M` for `𝐂`, `K` for `𝐀ᡀ`, and `N` for `𝐁ᡀ`. However, LoopVectorization and all the specialized matrix multiplication functions managed to do about as well as normal; transposing while storing the results takes negligible amounts of time relative to the matrix multiplication itself.
3737
The ifort-loop version also did fairly well.
38-
39-
40-
41-

0 commit comments

Comments
Β (0)