Skip to content

Commit

Permalink
Add content
Browse files Browse the repository at this point in the history
  • Loading branch information
pedropark99 committed Sep 28, 2024
1 parent d4b6da4 commit a6a9b25
Show file tree
Hide file tree
Showing 5 changed files with 29 additions and 27 deletions.
24 changes: 8 additions & 16 deletions Chapters/15-vectors.qmd
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,8 @@ knitr::opts_chunk$set(
# Introducing Vectors and SIMD {#sec-vectors-simd}

In this chapter, I'm going to discuss vectors in Zig, which are
related to SIMD operations. Before we dive into the subject, is worth
mentioning that vectors in Zig have no relationship with the `std::vector` class
from C++. Remember, as we discussed at @sec-dynamic-array,
dynamic/growable arrays in Zig are represented by the `std.ArrayList()` structure
from the Zig Standard Library.

related to SIMD operations (i.e. they have no relationship with the `std::vector` class
from C++).

## What is SIMD?

Expand All @@ -35,7 +31,7 @@ but the massive use of SIMD on normal desktop computers is somewhat recent. In t
was only used on "supercomputers models".

Most modern CPU models (from AMD, Intel, etc.) these days (either in a desktop or in a
notebook model) have support for SIMD operations. If you have a very old CPU model installed in your
notebook model) have support for SIMD operations. So, if you have a very old CPU model installed in your
computer, then, is possible that you have no support for SIMD operations in your computer.

Why people have started using SIMD on their software? The answer is performance.
Expand All @@ -58,9 +54,9 @@ these operators are applied element-wise and in parallel by default.

## Vectors {#sec-what-vectors}

In order to use SIMD operations, we usually call a "SIMD intrinsic", which is just a fancy
A SIMD operation is usually performed through a "SIMD intrinsic", which is just a fancy
name for a function that performs a SIMD operation. These SIMD intrinsics (or "SIMD functions")
always operate over a special type of object, which are called "vectors". In other words,
always operate over a special type of object, which are called "vectors". So,
in order to use SIMD, you have to create a "vector object".

A vector object is usually a fixed-sized block of 128 bits (16 bytes).
Expand All @@ -72,7 +68,8 @@ to accomodate more data into a single vector object.

You can create a new vector object in Zig by using the `@Vector()` built-in function. Inside this function,
you specify the vector length (number of elements in the vector), and the data type of the elements
of the vector. In the example below, I'm creating two vector objects (`v1` and `v2`) of 4 elements of type `u32` each.
of the vector. Only primitive data types are supported in these vector objects.
In the example below, I'm creating two vector objects (`v1` and `v2`) of 4 elements of type `u32` each.

Also notice in the example below, that a third vector object (`v3`) is created from the
sum of the previous two vector objects (`v1` plus `v2`). Therefore,
Expand Down Expand Up @@ -104,7 +101,7 @@ likely produce a similar performance from a "for loop solution".

There are different ways you can transform a normal array into a vector object.
You can either use implicit conversion (which is when you assign the array to
a vector object directly), or, slices to create a vector object from a normal array.
a vector object directly), or, use slices to create a vector object from a normal array.

In the example below, we implicitly convert the array `a1` into a vector object (`v1`)
of length 4. All we had to do was to just explicitly annotate the data type of the vector object,
Expand Down Expand Up @@ -176,9 +173,4 @@ Segmentation fault (core dumped)



## Matrix multiplication

Just as a quick example, SIMD operations .



4 changes: 2 additions & 2 deletions _freeze/Chapters/15-vectors/execute-results/html.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"hash": "313e4fab8030831397fb0bdd61f2336c",
"hash": "116a4477721450b6aaaf7368c1ababb8",
"result": {
"engine": "knitr",
"markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n\n\n\n\n\n# Introducing Vectors and SIMD {#sec-vectors-simd}\n\nIn this chapter, I'm going to discuss vectors in Zig, which are\nrelated to SIMD operations. Before we dive into the subject, is worth\nmentioning that vectors in Zig have no relationship with the `std::vector` class\nfrom C++. Remember, as we discussed at @sec-dynamic-array,\ndynamic/growable arrays in Zig are represented by the `std.ArrayList()` structure\nfrom the Zig Standard Library.\n\n\n## What is SIMD?\n\nSIMD (*Single Instruction/Multiple Data*) is a group of operations that are widely used\non video/audio editing programs, and also in graphics applications. SIMD is not a new technology,\nbut the massive use of SIMD on normal desktop computers is somewhat recent. In the old days, SIMD\nwas only used on \"supercomputers models\".\n\nMost modern CPU models (from AMD, Intel, etc.) these days (either in a desktop or in a\nnotebook model) have support for SIMD operations. If you have a very old CPU model installed in your\ncomputer, then, is possible that you have no support for SIMD operations in your computer.\n\nWhy people have started using SIMD on their software? The answer is performance.\nBut what SIMD precisely do to achieve better performance? Well, in essence, SIMD operations are a different\nstrategy to get parallel computing in your program, and therefore, make faster calculations.\n\nThe basic idea behind SIMD is to have a single instruction that operates over multiple data\nat the same time. When you perform a normal scalar operation, like for example, four add instructions,\neach addition is performed separately, one after another. But with SIMD, these four add instructions\nare translated into a single instruction, and, as consequence, the four additions are performed\nin parallel, at the same time.\n\nCurrently, the following group of operators are allowed to use on vector objects. All of\nthese operators are applied element-wise and in parallel by default.\n\n- Arithmetic (`+`, `-`, `/`, `*`, `@divFloor()`, `@sqrt()`, `@ceil()`, `@log()`, etc.).\n- Bitwise operators (`>>`, `<<`, `&`, `|`, `~`, etc.).\n- Comparison operators (`<`, `>`, `==`, etc.).\n\n\n## Vectors {#sec-what-vectors}\n\nIn order to use SIMD operations, we usually call a \"SIMD intrinsic\", which is just a fancy\nname for a function that performs a SIMD operation. These SIMD intrinsics (or \"SIMD functions\")\nalways operate over a special type of object, which are called \"vectors\". In other words,\nin order to use SIMD, you have to create a \"vector object\".\n\nA vector object is usually a fixed-sized block of 128 bits (16 bytes).\nAs consequence, most vectors that you find in the wild are essentially arrays that contains 2 values of 8 bytes each,\nor, 4 values of 4 bytes each, or, 8 values of 2 bytes each, etc.\nHowever, different CPU models may have different extensions (or, \"implementations\") of SIMD,\nwhich may offer more types of vector objects that are bigger in size (256 bits or 512 bits)\nto accomodate more data into a single vector object.\n\nYou can create a new vector object in Zig by using the `@Vector()` built-in function. Inside this function,\nyou specify the vector length (number of elements in the vector), and the data type of the elements\nof the vector. In the example below, I'm creating two vector objects (`v1` and `v2`) of 4 elements of type `u32` each.\n\nAlso notice in the example below, that a third vector object (`v3`) is created from the\nsum of the previous two vector objects (`v1` plus `v2`). Therefore,\nmath operations over vector objects take place element-wise by default, because\nthe same operation (in this case, addition) is transformed into a single instruction\nthat is replicated in parallel, across all elements of the vectors.\n\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = @Vector(4, u32){4, 12, 37, 9};\nconst v2 = @Vector(4, u32){10, 22, 5, 12};\nconst v3 = v1 + v2;\ntry stdout.print(\"{any}\\n\", .{v3});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 14, 34, 42, 21 }\n```\n\n\n:::\n:::\n\n\n\n\n\nThis is how SIMD introduces more performance in your program. Instead of using a for loop\nto iterate through the elements of `v1` and `v2`, and adding them together, one element at a time,\nwe enjoy the benefits of SIMD, which performs all 4 additions in parallel, at the same time.\n\nTherefore, the `@Vector` structure in Zig is essentially, the Zig representation of SIMD vector objects.\nBut the elements on these vector objects will be operated in parallel, if, and only if your current CPU model\nsupports SIMD operations. If your CPU model does not support SIMD, then, the `@Vector` structure will\nlikely produce a similar performance from a \"for loop solution\".\n\n\n### Transforming arrays into vectors\n\nThere are different ways you can transform a normal array into a vector object.\nYou can either use implicit conversion (which is when you assign the array to\na vector object directly), or, slices to create a vector object from a normal array.\n\nIn the example below, we implicitly convert the array `a1` into a vector object (`v1`)\nof length 4. All we had to do was to just explicitly annotate the data type of the vector object,\nand then, assign the array object to this vector object.\n\nAlso notice in the example below, that a second vector object (`v2`) is also created\nby taking a slice of the array object (`a1`), and then, storing the pointer to this\nslice (`.*`) into this vector object.\n\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst a1 = [4]u32{4, 12, 37, 9};\nconst v1: @Vector(4, u32) = a1;\nconst v2: @Vector(2, u32) = a1[1..3].*;\n_ = v1; _ = v2;\n```\n:::\n\n\n\n\n\n\nIs worth emphasizing that only arrays and slices whose sizes\nare compile-time known can be transformed into vectors. Vectors in general\nare structures that work only with compile-time known sizes. Therefore, if\nyou have an array whose size is runtime known, then, you first need to\ncopy it into an array with a compile-time known size, before transforming it into a vector.\n\n\n\n### The `@splat()` function\n\nYou can use the `@splat()` built-in function to create a vector object that is filled\nwith the same value across all of it's elements. This function was created to offer a quick\nand easy way to directly convert a scalar value (a.k.a. a single value, like a single character, or a single integer, etc.)\ninto a vector object.\n\nThus, we can use `@splat()` to convert a single value, like the integer `16` into a vector object\nof length 1. But we can also use this function to convert the same integer `16` into a\nvector object of length 10, that is filled with 10 `16` values. The example below demonstrates\nthis idea.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1: @Vector(10, u32) = @splat(16);\ntry stdout.print(\"{any}\\n\", .{v1});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }\n```\n\n\n:::\n:::\n\n\n\n\n\n\n\n### Careful with vectors that are too big\n\nAs I described at @sec-what-vectors, each vector object is usually a small block of 128, 256 or 512 bits.\nThis means that a vector object is usually small in size, and when you try to go in the opposite direction,\nby creating a vector object in Zig that is very big in size (i.e. sizes that are close to $2^{20}$),\nyou usually end up with crashes and loud errors from the compiler.\n\nFor example, if you try to compile the program below, you will likely face segmentation faults, or, LLVM errors during\nthe build process. Just be careful to not create vector objects that are too big in size.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1: @Vector(1000000, u32) = @splat(16);\n_ = v1;\n```\n:::\n\n\n\n\n\n```\nSegmentation fault (core dumped)\n```\n\n\n\n\n\n",
"markdown": "---\nengine: knitr\nknitr: true\nsyntax-definition: \"../Assets/zig.xml\"\n---\n\n\n\n\n\n\n\n\n\n\n# Introducing Vectors and SIMD {#sec-vectors-simd}\n\nIn this chapter, I'm going to discuss vectors in Zig, which are\nrelated to SIMD operations (i.e. they have no relationship with the `std::vector` class\nfrom C++).\n\n## What is SIMD?\n\nSIMD (*Single Instruction/Multiple Data*) is a group of operations that are widely used\non video/audio editing programs, and also in graphics applications. SIMD is not a new technology,\nbut the massive use of SIMD on normal desktop computers is somewhat recent. In the old days, SIMD\nwas only used on \"supercomputers models\".\n\nMost modern CPU models (from AMD, Intel, etc.) these days (either in a desktop or in a\nnotebook model) have support for SIMD operations. So, if you have a very old CPU model installed in your\ncomputer, then, is possible that you have no support for SIMD operations in your computer.\n\nWhy people have started using SIMD on their software? The answer is performance.\nBut what SIMD precisely do to achieve better performance? Well, in essence, SIMD operations are a different\nstrategy to get parallel computing in your program, and therefore, make faster calculations.\n\nThe basic idea behind SIMD is to have a single instruction that operates over multiple data\nat the same time. When you perform a normal scalar operation, like for example, four add instructions,\neach addition is performed separately, one after another. But with SIMD, these four add instructions\nare translated into a single instruction, and, as consequence, the four additions are performed\nin parallel, at the same time.\n\nCurrently, the following group of operators are allowed to use on vector objects. All of\nthese operators are applied element-wise and in parallel by default.\n\n- Arithmetic (`+`, `-`, `/`, `*`, `@divFloor()`, `@sqrt()`, `@ceil()`, `@log()`, etc.).\n- Bitwise operators (`>>`, `<<`, `&`, `|`, `~`, etc.).\n- Comparison operators (`<`, `>`, `==`, etc.).\n\n\n## Vectors {#sec-what-vectors}\n\nA SIMD operation is usually performed through a \"SIMD intrinsic\", which is just a fancy\nname for a function that performs a SIMD operation. These SIMD intrinsics (or \"SIMD functions\")\nalways operate over a special type of object, which are called \"vectors\". So,\nin order to use SIMD, you have to create a \"vector object\".\n\nA vector object is usually a fixed-sized block of 128 bits (16 bytes).\nAs consequence, most vectors that you find in the wild are essentially arrays that contains 2 values of 8 bytes each,\nor, 4 values of 4 bytes each, or, 8 values of 2 bytes each, etc.\nHowever, different CPU models may have different extensions (or, \"implementations\") of SIMD,\nwhich may offer more types of vector objects that are bigger in size (256 bits or 512 bits)\nto accomodate more data into a single vector object.\n\nYou can create a new vector object in Zig by using the `@Vector()` built-in function. Inside this function,\nyou specify the vector length (number of elements in the vector), and the data type of the elements\nof the vector. Only primitive data types are supported in these vector objects.\nIn the example below, I'm creating two vector objects (`v1` and `v2`) of 4 elements of type `u32` each.\n\nAlso notice in the example below, that a third vector object (`v3`) is created from the\nsum of the previous two vector objects (`v1` plus `v2`). Therefore,\nmath operations over vector objects take place element-wise by default, because\nthe same operation (in this case, addition) is transformed into a single instruction\nthat is replicated in parallel, across all elements of the vectors.\n\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1 = @Vector(4, u32){4, 12, 37, 9};\nconst v2 = @Vector(4, u32){10, 22, 5, 12};\nconst v3 = v1 + v2;\ntry stdout.print(\"{any}\\n\", .{v3});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 14, 34, 42, 21 }\n```\n\n\n:::\n:::\n\n\n\n\n\nThis is how SIMD introduces more performance in your program. Instead of using a for loop\nto iterate through the elements of `v1` and `v2`, and adding them together, one element at a time,\nwe enjoy the benefits of SIMD, which performs all 4 additions in parallel, at the same time.\n\nTherefore, the `@Vector` structure in Zig is essentially, the Zig representation of SIMD vector objects.\nBut the elements on these vector objects will be operated in parallel, if, and only if your current CPU model\nsupports SIMD operations. If your CPU model does not support SIMD, then, the `@Vector` structure will\nlikely produce a similar performance from a \"for loop solution\".\n\n\n### Transforming arrays into vectors\n\nThere are different ways you can transform a normal array into a vector object.\nYou can either use implicit conversion (which is when you assign the array to\na vector object directly), or, use slices to create a vector object from a normal array.\n\nIn the example below, we implicitly convert the array `a1` into a vector object (`v1`)\nof length 4. All we had to do was to just explicitly annotate the data type of the vector object,\nand then, assign the array object to this vector object.\n\nAlso notice in the example below, that a second vector object (`v2`) is also created\nby taking a slice of the array object (`a1`), and then, storing the pointer to this\nslice (`.*`) into this vector object.\n\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst a1 = [4]u32{4, 12, 37, 9};\nconst v1: @Vector(4, u32) = a1;\nconst v2: @Vector(2, u32) = a1[1..3].*;\n_ = v1; _ = v2;\n```\n:::\n\n\n\n\n\n\nIs worth emphasizing that only arrays and slices whose sizes\nare compile-time known can be transformed into vectors. Vectors in general\nare structures that work only with compile-time known sizes. Therefore, if\nyou have an array whose size is runtime known, then, you first need to\ncopy it into an array with a compile-time known size, before transforming it into a vector.\n\n\n\n### The `@splat()` function\n\nYou can use the `@splat()` built-in function to create a vector object that is filled\nwith the same value across all of it's elements. This function was created to offer a quick\nand easy way to directly convert a scalar value (a.k.a. a single value, like a single character, or a single integer, etc.)\ninto a vector object.\n\nThus, we can use `@splat()` to convert a single value, like the integer `16` into a vector object\nof length 1. But we can also use this function to convert the same integer `16` into a\nvector object of length 10, that is filled with 10 `16` values. The example below demonstrates\nthis idea.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1: @Vector(10, u32) = @splat(16);\ntry stdout.print(\"{any}\\n\", .{v1});\n```\n\n\n::: {.cell-output .cell-output-stdout}\n\n```\n{ 16, 16, 16, 16, 16, 16, 16, 16, 16, 16 }\n```\n\n\n:::\n:::\n\n\n\n\n\n\n\n### Careful with vectors that are too big\n\nAs I described at @sec-what-vectors, each vector object is usually a small block of 128, 256 or 512 bits.\nThis means that a vector object is usually small in size, and when you try to go in the opposite direction,\nby creating a vector object in Zig that is very big in size (i.e. sizes that are close to $2^{20}$),\nyou usually end up with crashes and loud errors from the compiler.\n\nFor example, if you try to compile the program below, you will likely face segmentation faults, or, LLVM errors during\nthe build process. Just be careful to not create vector objects that are too big in size.\n\n\n\n\n\n::: {.cell}\n\n```{.zig .cell-code}\nconst v1: @Vector(1000000, u32) = @splat(16);\n_ = v1;\n```\n:::\n\n\n\n\n\n```\nSegmentation fault (core dumped)\n```\n\n\n\n\n",
"supporting": [],
"filters": [
"rmarkdown/pagebreak.lua"
Expand Down
Loading

0 comments on commit a6a9b25

Please sign in to comment.