Просмотр исходного кода

Merge branch 'main' into readme

Chris Boesch 9 месяцев назад
Родитель
Сommit
fb46871dab

+ 0 - 31
.devcontainer/devcontainer.json

@@ -1,31 +0,0 @@
-// For format details, see https://aka.ms/devcontainer.json. For config options, see the
-// README at: https://github.com/devcontainers/templates/tree/main/src/debian
-{
-	"name": "Ziglings",
-	// Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile
-	"image": "mcr.microsoft.com/devcontainers/base:bullseye",
-	"features": {
-		"ghcr.io/devcontainers-contrib/features/zig:1": {
-			"version": "master"
-		}
-	},
-	"customizations": {
-		"vscode": {
-			"extensions": [
-				"ziglang.vscode-zig"
-			]
-		}
-	}
-
-	// Features to add to the dev container. More info: https://containers.dev/features.
-	// "features": {},
-
-	// Use 'forwardPorts' to make a list of ports inside the container available locally.
-	// "forwardPorts": [],
-
-	// Configure tool-specific properties.
-	// "customizations": {},
-
-	// Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root.
-	// "remoteUser": "root"
-}

+ 7 - 0
build.zig

@@ -1202,6 +1202,13 @@ const exercises = [_]Exercise{
         .output = "The pull request has been merged.",
     },
     .{
+        .main_file = "109_vectors.zig",
+        .output =
+        \\Max difference (old fn): 0.014
+        \\Max difference (new fn): 0.014
+        ,
+    },
+    .{
         .main_file = "999_the_end.zig",
         .output =
         \\

+ 1 - 1
exercises/058_quiz7.zig

@@ -190,7 +190,7 @@ const TripItem = union(enum) {
     fn printMe(self: TripItem) void {
         switch (self) {
             // Oops! The hermit forgot how to capture the union values
-            // in a switch statement. Please capture both values as
+            // in a switch statement. Please capture each value as
             // 'p' so the print statements work!
             .place => print("{s}", .{p.name}),
             .path => print("--{}->", .{p.dist}),

+ 11 - 9
exercises/065_builtins2.zig

@@ -110,15 +110,15 @@ pub fn main() void {
     // name will not be printed if the field is of type 'void'
     // (which is a zero-bit type that takes up no space at all!):
     if (fields[0].??? != void) {
-        print(" {s}", .{@typeInfo(Narcissus).@"struct".fields[0].name});
+        print(" {s}", .{fields[0].name});
     }
 
     if (fields[1].??? != void) {
-        print(" {s}", .{@typeInfo(Narcissus).@"struct".fields[1].name});
+        print(" {s}", .{fields[1].name});
     }
 
     if (fields[2].??? != void) {
-        print(" {s}", .{@typeInfo(Narcissus).@"struct".fields[2].name});
+        print(" {s}", .{fields[2].name});
     }
 
     // Yuck, look at all that repeated code above! I don't know
@@ -136,14 +136,16 @@ pub fn main() void {
 // But a change after Zig 0.10.0 added the source file name to the
 // type. "Narcissus" became "065_builtins2.Narcissus".
 //
-// To fix this, I've added this function to strip the filename from
-// the front of the type name in the dumbest way possible. (It returns
-// a slice of the type name starting at character 14 (assuming
-// single-byte characters).
+// To fix this, we've added this function to strip the filename from
+// the front of the type name. (It returns a slice of the type name
+// starting at the index + 1 of character ".")
 //
 // We'll be seeing @typeName again in Exercise 070. For now, you can
 // see that it takes a Type and returns a u8 "string".
 fn maximumNarcissism(myType: anytype) []const u8 {
-    // Turn '065_builtins2.Narcissus' into 'Narcissus'
-    return @typeName(myType)[14..];
+    const indexOf = @import("std").mem.indexOf;
+
+    // Turn "065_builtins2.Narcissus" into "Narcissus"
+    const name = @typeName(myType);
+    return name[indexOf(u8, name, ".").? + 1 ..];
 }

+ 1 - 1
exercises/099_formatting.zig

@@ -16,7 +16,7 @@
 // Therefore, the comments for the format() function are the only
 // way to definitively learn how to format strings in Zig:
 //
-//     https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L29
+//     https://github.com/ziglang/zig/blob/master/lib/std/fmt.zig#L33
 //
 // Zig already has a very nice selection of formatting options.
 // These can be used in different ways, but generally to convert

+ 1 - 1
exercises/105_threading2.zig

@@ -39,7 +39,7 @@
 // in practice. Because either you don't need the precision, or you use a
 // calculator in which the number is stored as a very precise constant.
 // But at some point this constant was calculated and we are doing the same
-// now.The question at this point is, how many partial values do we have
+// now. The question at this point is, how many partial values do we have
 // to calculate for which accuracy?
 //
 // The answer is chewing, to get 8 digits after the decimal point we need

+ 147 - 0
exercises/109_vectors.zig

@@ -0,0 +1,147 @@
+// So far in Ziglings, we've seen how for loops can be used to
+// repeat calculations across an array in several ways.
+//
+// For loops are generally great for this kind of task, but
+// sometimes they don't fully utilize the capabilities of the
+// CPU.
+//
+// Most modern CPUs can execute instructions in which SEVERAL
+// calculations are performed WITHIN registers at the SAME TIME.
+// These are known as "single instruction, multiple data" (SIMD)
+// instructions. SIMD instructions can make code significantly
+// more performant.
+//
+// To see why, imagine we have a program in which we take the
+// square root of four (changing) f32 floats.
+//
+// A simple compiler would take the program and produce machine code
+// which calculates each square root sequentially. Most registers on
+// modern CPUs have 64 bits, so we could imagine that each float moves
+// into a 64-bit register, and the following happens four times:
+//
+//            32 bits   32 bits
+//          +-------------------+
+// register |    0    |    x    |
+//          +-------------------+
+//
+//                    |
+//           [SQRT instruction]
+//                    V
+//
+//          +-------------------+
+//          |    0    | sqrt(x) |
+//          +-------------------+
+//
+// Notice that half of the register contains blank data to which
+// nothing happened. What a waste! What if we were able to use
+// that space instead? This is the idea at the core of SIMD.
+//
+// Most modern CPUs contain specialized registers with at least 128 bits
+// for performing SIMD instructions. On a machine with 128-bit SIMD
+// registers, a smart compiler would probably NOT issue four sqrt
+// instructions as above, but instead pack the floats into a single
+// 128-bit register, then execute a single "packed" sqrt
+// instruction to do ALL the square root calculations at once.
+//
+// For example:
+//
+//
+//             32 bits   32 bits   32 bits   32 bits
+//           +---------------------------------------+
+// register  |   4.0   |   9.0   |  25.0   |  49.0   |
+//           +---------------------------------------+
+//
+//                              |
+//                  [SIMD SQRT instruction]
+//                              V
+//
+//           +---------------------------------------+
+// register  |   2.0   |   3.0   |   5.0   |   7.0   |
+//           +---------------------------------------+
+//
+// Pretty cool, right?
+//
+// Code with SIMD instructions is usually more performant than code
+// without SIMD instructions. Zig cares a lot about performance,
+// so it has built-in support for SIMD! It has a data structure that
+// directly supports SIMD instructions:
+//
+//                        +-----------+
+//                        |  Vectors  |
+//                        +-----------+
+//
+// Operations performed on vectors in Zig will be done in parallel using
+// SIMD instructions, whenever possible.
+//
+// Defining vectors in Zig is straightforwards. No library import is needed.
+const v1 = @Vector(3, i32){ 1, 10, 100 };
+const v2 = @Vector(3, f32){ 2.0, 3.0, 5.0 };
+
+// Vectors support the same builtin operators as their underlying base types.
+const v3 = v1 + v1; // {   2,  20,  200};
+const v4 = v2 * v2; // { 4.0, 9.0, 25.0};
+
+// Intrinsics that apply to base types usually extend to vectors.
+const v5: @Vector(3, f32) = @floatFromInt(v3); // { 2.0,  20.0,  200.0}
+const v6 = v4 - v5; // { 2.0, -11.0, -175.0}
+const v7 = @abs(v6); // { 2.0,  11.0,  175.0}
+
+// We can make constant vectors, and reduce vectors.
+const v8: @Vector(4, u8) = @splat(2); // { 2, 2, 2, 2}
+const v8_sum = @reduce(.Add, v8); // 8
+const v8_min = @reduce(.Min, v8); // 2
+
+// Fixed-length arrays can be automatically assigned to vectors (and vice-versa).
+const single_digit_primes = [4]i8{ 2, 3, 5, 7 };
+const prime_vector: @Vector(4, i8) = single_digit_primes;
+
+// Now let's use vectors to simplify and optimize some code!
+//
+// Ewa is writing a program in which they frequently want to compare
+// two lists of four f32s. Ewa expects the lists to be similar, and
+// wants to determine the largest pairwise difference between the lists.
+//
+// Ewa wrote the following function to figure this out.
+
+fn calcMaxPairwiseDiffOld(list1: [4]f32, list2: [4]f32) f32 {
+    var max_diff: f32 = 0;
+    for (list1, list2) |n1, n2| {
+        const abs_diff = @abs(n1 - n2);
+        if (abs_diff > max_diff) {
+            max_diff = abs_diff;
+        }
+    }
+    return max_diff;
+}
+
+// Ewa heard about vectors in Zig, and started writing a new vector
+// version of the function, but has got stuck!
+//
+// Help Ewa finish the vector version! The examples above should help.
+
+const Vec4 = @Vector(4, f32);
+fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
+    const abs_diff_vec = ???;
+    const max_diff = @reduce(???, abs_diff_vec);
+    return max_diff;
+}
+
+// Quite the simplification! We could even write the function in one line
+// and it would still be readable.
+//
+// Since the entire function is now expressed in terms of vector operations,
+// the Zig compiler will easily be able to compile it down to machine code
+// which utilizes the all-powerful SIMD instructions and does a lot of the
+// computation in parallel.
+
+const std = @import("std");
+const print = std.debug.print;
+
+pub fn main() void {
+    const l1 = [4]f32{ 3.141, 2.718, 0.577, 1.000 };
+    const l2 = [4]f32{ 3.154, 2.707, 0.591, 0.993 };
+    const mpd_old = calcMaxPairwiseDiffOld(l1, l2);
+    const mpd_new = calcMaxPairwiseDiffNew(l1, l2);
+    print("Max difference (old fn): {d: >5.3}\n", .{mpd_old});
+    print("Max difference (new fn): {d: >5.3}\n", .{mpd_new});
+}

+ 3 - 3
patches/patches/058_quiz7.patch

@@ -1,8 +1,8 @@
---- exercises/058_quiz7.zig	2023-10-03 22:15:22.125574535 +0200
-+++ answers/058_quiz7.zig	2023-10-05 20:04:07.106101152 +0200
+--- exercises/058_quiz7.zig	2024-10-28 09:06:49.448505460 +0100
++++ answers/058_quiz7.zig	2024-10-28 09:35:14.631932322 +0100
 @@ -192,8 +192,8 @@
              // Oops! The hermit forgot how to capture the union values
-             // in a switch statement. Please capture both values as
+             // in a switch statement. Please capture each value as
              // 'p' so the print statements work!
 -            .place => print("{s}", .{p.name}),
 -            .path => print("--{}->", .{p.dist}),

+ 5 - 5
patches/patches/065_builtins2.patch

@@ -1,5 +1,5 @@
---- exercises/065_builtins2.zig	2024-09-02 19:15:56.569952315 +0200
-+++ answers/065_builtins2.zig	2024-09-02 19:13:44.280600350 +0200
+--- exercises/065_builtins2.zig	2024-11-02 16:58:30.607829441 +0100
++++ answers/065_builtins2.zig	2024-11-02 16:58:33.821220588 +0100
 @@ -58,7 +58,7 @@
      // Oops! We cannot leave the 'me' and 'myself' fields
      // undefined. Please set them here:
@@ -24,16 +24,16 @@
      // (which is a zero-bit type that takes up no space at all!):
 -    if (fields[0].??? != void) {
 +    if (fields[0].type != void) {
-         print(" {s}", .{@typeInfo(Narcissus).@"struct".fields[0].name});
+         print(" {s}", .{fields[0].name});
      }
  
 -    if (fields[1].??? != void) {
 +    if (fields[1].type != void) {
-         print(" {s}", .{@typeInfo(Narcissus).@"struct".fields[1].name});
+         print(" {s}", .{fields[1].name});
      }
  
 -    if (fields[2].??? != void) {
 +    if (fields[2].type != void) {
-         print(" {s}", .{@typeInfo(Narcissus).@"struct".fields[2].name});
+         print(" {s}", .{fields[2].name});
      }
  

+ 2 - 2
patches/patches/099_formatting.patch

@@ -1,5 +1,5 @@
---- exercises/099_formatting.zig	2023-10-03 22:15:22.125574535 +0200
-+++ answers/099_formatting.zig	2023-10-05 20:04:07.292771311 +0200
+--- exercises/099_formatting.zig	2024-11-07 21:45:10.459123650 +0100
++++ answers/099_formatting.zig	2024-11-07 21:43:55.154345991 +0100
 @@ -131,7 +131,7 @@
          for (0..size) |b| {
              // What formatting is needed here to make our columns

+ 13 - 0
patches/patches/109_vectors.patch

@@ -0,0 +1,13 @@
+--- exercises/109_vectors.zig	2024-11-07 14:57:09.673383618 +0100
++++ answers/109_vectors.zig	2024-11-07 14:22:59.069150138 +0100
+@@ -121,8 +121,8 @@
+ 
+ const Vec4 = @Vector(4, f32);
+ fn calcMaxPairwiseDiffNew(a: Vec4, b: Vec4) f32 {
+-    const abs_diff_vec = ???;
+-    const max_diff = @reduce(???, abs_diff_vec);
++    const abs_diff_vec = @abs(a - b);
++    const max_diff = @reduce(.Max, abs_diff_vec);
+     return max_diff;
+ }
+