Benchmark sources
These are the programs behind the performance figures, each written four times with the same algorithm and the same data. Choose a language on any of them and every one switches to it.
The xc versions are plain loops over arrays and objects. None of them asks for
vector instructions, threads, a matrix unit or anything else by name: where
matrix_mul_f32 runs on Apple’s SME matrix unit, or array_map on AVX-512,
it is because xcc saw the loop and chose to.
Each program times its own work with bench_now_us() (the monotonic clock) and
prints a checksum, which must agree across the four languages for a run to
count.
arc_alloc
Section titled “arc_alloc”Allocate an object per iteration and let ARC free it.
Lines: xc 31, Objective-C 35, C++ 34, Swift 24.
// arc_alloc — allocate an object per iteration and let ARC free it.// Exercises: heap allocation, retain/release traffic, method dispatch.// Each object outlives its iteration (it is kept until the next one replaces// it), so no compiler can put it on the stack or fold the loop away, and the// result is folded in with ^ so the sum has no closed form.#import "Stdio.xc"#import "include/bench_time.xc"
class Node { u32 v; void init(u32 x) { v = x; } u32 get(void) { return v; } }
i32 main(i32 argc, u8** argv) { u32 seed = (u32)argc; u32 acc = (u32)0; Node* keep = new Node(seed); i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)80000000; r++) { Node* n = new Node(r + seed); acc = (acc ^ n.get()) + keep.get(); keep = n; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// arc_alloc — allocate an object per iteration and let ARC free it.// See arc_alloc.xc. Same object shape, same count, same accumulation.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>
@interface Node : NSObject@property (nonatomic) uint32_t v;- (instancetype)initWithV:(uint32_t)x;@end
@implementation Node- (instancetype)initWithV:(uint32_t)x { if ((self = [super init])) _v = x; return self; }- (uint32_t)get { return _v; }@end
int main(int argc, char **argv) { @autoreleasepool { uint32_t seed = (uint32_t)argc; uint32_t acc = 0; Node *keep = [[Node alloc] initWithV:seed]; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 80000000; r++) { Node *n = [[Node alloc] initWithV:r + seed]; acc = (acc ^ [n get]) + [keep get]; keep = n; } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// arc_alloc — allocate an object per iteration and let ARC free it.// See arc_alloc.xc. Same object shape, same count, same accumulation.// Each object is a std::shared_ptr from std::make_shared, so the C++ version// also pays for a heap allocation and reference counting per iteration.#include <cstdio>#include <cstdint>#include <memory>#include "include/bench_time.h"
class Node {public: explicit Node(uint32_t x) : v_(x) {} uint32_t get() const { return v_; }private: uint32_t v_; };
int main(int argc, char **argv) { const uint32_t seed = static_cast<uint32_t>(argc); uint32_t acc = 0; std::shared_ptr<Node> keep = std::make_shared<Node>(seed); int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 80000000u; r++) { std::shared_ptr<Node> n = std::make_shared<Node>(r + seed); acc = (acc ^ n->get()) + keep->get(); keep = n; } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// arc_alloc — allocate an object per iteration and let ARC free it. See arc_alloc.xc.
final class Node { let v: UInt32 init(_ x: UInt32) { v = x } func get() -> UInt32 { v }}
@mainstruct ArcAlloc { static func main() { let seed = UInt32(CommandLine.arguments.count) var acc: UInt32 = 0 var keep = Node(seed) let t0 = bench_now_us() for r in 0..<UInt32(80_000_000) { let n = Node(r &+ seed) acc = (acc ^ n.get()) &+ keep.get() keep = n } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}arc_array
Section titled “arc_array”Hold objects in an array and walk them.
Lines: xc 21, Objective-C 31, C++ 34, Swift 25.
// arc_array — hold objects in an array and walk them.// Exercises: retain/release on stored references, field access through a pointer.#import "Stdio.xc"#import "include/bench_time.xc"#define N 1024
class Cell { u32 v; void init(u32 x) { v = x; } u32 get(void) { return v; } }
i32 main(i32 argc, u8** argv) { u32 seed = (u32)argc; Cell* cells[N]; for (u32 i = (u32)0; i < (u32)N; i++) cells[i] = new Cell(i + seed); u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)3000000; r++) for (u32 i = (u32)0; i < (u32)N; i++) acc = acc + cells[i].get(); i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// arc_array — hold objects in an array and walk them. See arc_array.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 1024
@interface Cell : NSObject- (instancetype)initWithV:(uint32_t)x;- (uint32_t)get;@end@implementation Cell { uint32_t _vv; }- (instancetype)initWithV:(uint32_t)x { if ((self = [super init])) _vv = x; return self; }- (uint32_t)get { return _vv; }@end
int main(int argc, char **argv) { @autoreleasepool { uint32_t seed = (uint32_t)argc; Cell * __strong cells[N]; for (uint32_t i = 0; i < N; i++) cells[i] = [[Cell alloc] initWithV:i + seed]; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3000000; r++) for (uint32_t i = 0; i < N; i++) acc = acc + [cells[i] get]; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// arc_array — hold objects in an array and walk them. See arc_array.xc.// The objects are held as std::shared_ptr in a std::vector, so storing them// pays for reference counting as the ARC version does.#include <cstdio>#include <cstdint>#include <memory>#include <vector>#include "include/bench_time.h"
constexpr uint32_t N = 1024;
class Cell {public: explicit Cell(uint32_t x) : v_(x) {} uint32_t get() const { return v_; }private: uint32_t v_; };
int main(int argc, char **argv) { const uint32_t seed = static_cast<uint32_t>(argc); std::vector<std::shared_ptr<Cell>> cells; cells.reserve(N); for (uint32_t i = 0; i < N; i++) cells.push_back(std::make_shared<Cell>(i + seed)); uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3000000u; r++) for (const auto &cell : cells) acc = acc + cell->get(); int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// arc_array — hold objects in an array and walk them. See arc_array.xc.
final class Cell { let v: UInt32 init(_ x: UInt32) { v = x } func get() -> UInt32 { v }}
@mainstruct ArcArray { static func main() { let n = 1024 let seed = UInt32(CommandLine.arguments.count) var cells: [Cell] = [] cells.reserveCapacity(n) for i in 0..<n { cells.append(Cell(UInt32(i) &+ seed)) } var acc: UInt32 = 0 let t0 = bench_now_us() for _ in 0..<UInt32(3_000_000) { for i in 0..<n { acc = acc &+ cells[i].get() } } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}array_map
Section titled “array_map”Elementwise c[i] = a[i] + b[i] * k.
Lines: xc 21, Objective-C 22, C++ 22, Swift 24.
// array_map — elementwise c[i] = a[i] + b[i] * k.// Exercises: elementwise map loop, the shape the arm64 map vectoriser takes.#import "Stdio.xc"#import "include/bench_time.xc"#define N 4096i32 main(i32 argc, u8** argv) { u32 a[N]; u32 b[N]; u32 c[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) { a[i] = i + seed; b[i] = (i * (u32)3) + seed; } i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)5000000; r++) for (u32 i = (u32)0; i < (u32)N; i++) c[i] = a[i] + (b[i] * (u32)7) + r; u32 acc = (u32)0; for (u32 i = (u32)0; i < (u32)N; i++) acc = acc + c[i]; i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// array_map — elementwise c[i] = a[i] + b[i] * k. See array_map.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 4096int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N], b[N], c[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) { a[i] = i + seed; b[i] = (i * 3u) + seed; } int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 5000000; r++) for (uint32_t i = 0; i < N; i++) c[i] = a[i] + (b[i] * 7u) + r; uint32_t acc = 0; for (uint32_t i = 0; i < N; i++) acc = acc + c[i]; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// array_map — elementwise c[i] = a[i] + b[i] * k. See array_map.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<uint32_t, N> a, b, c; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) { a[i] = i + seed; b[i] = (i * 3u) + seed; } int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 5000000u; r++) for (uint32_t i = 0; i < N; i++) c[i] = a[i] + (b[i] * 7u) + r; uint32_t acc = 0; for (uint32_t v : c) acc = acc + v; int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// array_map — elementwise c[i] = a[i] + b[i] * k. See array_map.xc.
@mainstruct ArrayMap { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) var b = [UInt32](repeating: 0, count: n) var c = [UInt32](repeating: 0, count: n) for i in 0..<n { a[i] = UInt32(i) &+ seed b[i] = (UInt32(i) &* 3) &+ seed } let t0 = bench_now_us() for r in 0..<UInt32(5_000_000) { for i in 0..<n { c[i] = a[i] &+ (b[i] &* 7) &+ r } } var acc: UInt32 = 0 for i in 0..<n { acc = acc &+ c[i] } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}array_sum
Section titled “array_sum”Sum an array. Exercises: reduction loop, array addressing.
Lines: xc 18, Objective-C 20, C++ 21, Swift 18.
// array_sum — sum an array. Exercises: reduction loop, array addressing.// This is the shape the arm64 reduction vectoriser recognises.#import "Stdio.xc"#import "include/bench_time.xc"#define N 4096i32 main(i32 argc, u8** argv) { u32 a[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) a[i] = (i * (u32)2654435761) + seed; u32 sum = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)7000000; r++) for (u32 i = (u32)0; i < (u32)N; i++) sum = sum + a[i]; i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", sum, t1 - t0); return 0; }// array_sum — sum an array. See array_sum.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 4096int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t sum = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 7000000; r++) for (uint32_t i = 0; i < N; i++) sum = sum + a[i]; int64_t t1 = bench_now_us(); printf("%u %lld\n", sum, (long long)(t1 - t0)); } return 0; }// array_sum — sum an array. See array_sum.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<uint32_t, N> a; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t sum = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 7000000u; r++) for (uint32_t v : a) sum = sum + v; int64_t t1 = bench_now_us(); std::printf("%u %lld\n", sum, static_cast<long long>(t1 - t0)); return 0; }// array_sum — sum an array. See array_sum.xc.
@mainstruct ArraySum { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) for i in 0..<n { a[i] = (UInt32(i) &* 2654435761) &+ seed } var sum: UInt32 = 0 let t0 = bench_now_us() for _ in 0..<UInt32(7_000_000) { for i in 0..<n { sum = sum &+ a[i] } } let t1 = bench_now_us() print("\(sum) \(t1 - t0)") }}bit_ops
Section titled “bit_ops”Shifts and bitwise logic over an array.
Lines: xc 19, Objective-C 21, C++ 22, Swift 21.
// bit_ops — shifts and bitwise logic over an array.// Exercises: shift lowering, and/or/xor, rotate idioms.#import "Stdio.xc"#import "include/bench_time.xc"#define N 4096i32 main(i32 argc, u8** argv) { u32 a[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) a[i] = (i * (u32)2654435761) + seed; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)600000; r++) for (u32 i = (u32)0; i < (u32)N; i++) acc = acc + (((a[i] + r) << (u32)3) | ((a[i] + r) >> (u32)5)) ^ (u32)0x0F0F0F0F; i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// bit_ops — shifts and bitwise logic over an array. See bit_ops.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 4096int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 600000; r++) for (uint32_t i = 0; i < N; i++) acc = acc + (((a[i] + r) << 3) | ((a[i] + r) >> 5)) ^ 0x0F0F0F0Fu; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// bit_ops — shifts and bitwise logic over an array. See bit_ops.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<uint32_t, N> a; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 600000u; r++) for (uint32_t v : a) acc = acc + (((v + r) << 3) | ((v + r) >> 5)) ^ 0x0F0F0F0Fu; int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// bit_ops — shifts and bitwise logic over an array. See bit_ops.xc.
@mainstruct BitOps { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) for i in 0..<n { a[i] = (UInt32(i) &* 2654435761) &+ seed } var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<UInt32(600_000) { for i in 0..<n { // C precedence: + binds tighter than ^, so this is (acc + (...)) ^ mask. acc = (acc &+ (((a[i] &+ r) &<< 3) | ((a[i] &+ r) &>> 5))) ^ 0x0F0F_0F0F } } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}branch_mix
Section titled “branch_mix”Data-dependent branches over an array.
Lines: xc 22, Objective-C 21, C++ 22, Swift 20.
// branch_mix — data-dependent branches over an array.// Exercises: if-conversion, select formation, branch prediction behaviour.#import "Stdio.xc"#import "include/bench_time.xc"#define N 4096i32 main(i32 argc, u8** argv) { u32 a[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) a[i] = (i * (u32)2654435761) + seed; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)500000; r++) for (u32 i = (u32)0; i < (u32)N; i++) { if ((a[i] & (u32)1) == (u32)0) acc = acc + a[i]; else acc = acc ^ a[i]; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// branch_mix — data-dependent branches over an array. See branch_mix.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 4096int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 500000; r++) for (uint32_t i = 0; i < N; i++) { if ((a[i] & 1u) == 0u) acc = acc + a[i]; else acc = acc ^ a[i]; } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// branch_mix — data-dependent branches over an array. See branch_mix.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<uint32_t, N> a; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 500000u; r++) for (uint32_t v : a) { if ((v & 1u) == 0u) acc = acc + v; else acc = acc ^ v; } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// branch_mix — data-dependent branches over an array. See branch_mix.xc.
@mainstruct BranchMix { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) for i in 0..<n { a[i] = (UInt32(i) &* 2654435761) &+ seed } var acc: UInt32 = 0 let t0 = bench_now_us() for _ in 0..<UInt32(500_000) { for i in 0..<n { if (a[i] & 1) == 0 { acc = acc &+ a[i] } else { acc = acc ^ a[i] } } } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}call_depth
Section titled “call_depth”A small non-inlinable call chain in a hot loop.
Lines: xc 19, Objective-C 22, C++ 22, Swift 17.
// call_depth — a small non-inlinable call chain in a hot loop.// Exercises: call overhead, leaf inlining, tail-call conversion.#import "Stdio.xc"#import "include/bench_time.xc"
u32 leaf(u32 x) { return (x * (u32)3) ^ (x >> (u32)2); }u32 mid(u32 x) { return leaf(x) + leaf(x + (u32)1); }u32 outer(u32 x) { return mid(x) ^ mid(x + (u32)2); }
i32 main(i32 argc, u8** argv) { u32 seed = (u32)argc; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)2800000000; r++) acc = acc + outer(r + seed); i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// call_depth — a small non-inlinable call chain in a hot loop. See call_depth.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>
static uint32_t leaf(uint32_t x) { return (x * 3u) ^ (x >> 2); }static uint32_t mid(uint32_t x) { return leaf(x) + leaf(x + 1u); }static uint32_t outer_(uint32_t x){ return mid(x) ^ mid(x + 2u); }
int main(int argc, char **argv) { @autoreleasepool { uint32_t seed = (uint32_t)argc; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 2800000000; r++) acc = acc + outer_(r + seed); int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// call_depth — a small non-inlinable call chain in a hot loop. See call_depth.xc.#include <cstdio>#include <cstdint>#include "include/bench_time.h"
namespace { uint32_t leaf(uint32_t x) { return (x * 3u) ^ (x >> 2); } uint32_t mid(uint32_t x) { return leaf(x) + leaf(x + 1u); } uint32_t outer(uint32_t x) { return mid(x) ^ mid(x + 2u); } }
int main(int argc, char **argv) { const uint32_t seed = static_cast<uint32_t>(argc); uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 2800000000u; r++) acc = acc + outer(r + seed); int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// call_depth — a small non-inlinable call chain in a hot loop. See call_depth.xc.
func leaf(_ x: UInt32) -> UInt32 { (x &* 3) ^ (x >> 2) }func mid(_ x: UInt32) -> UInt32 { leaf(x) &+ leaf(x &+ 1) }func outer(_ x: UInt32) -> UInt32 { mid(x) ^ mid(x &+ 2) }
@mainstruct CallDepth { static func main() { let seed = UInt32(CommandLine.arguments.count) var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<UInt32(2_800_000_000) { acc = acc &+ outer(r &+ seed) } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}float_math
Section titled “float_math”Float multiply, accumulated in double.
Lines: xc 29, Objective-C 24, C++ 28, Swift 24.
// float_math — float multiply, accumulated in double.// Exercises: float arithmetic, multiply-add fusion, float register pressure.//// Values are small exact integers and the accumulator is double, so the sum is// exact whatever order the additions happen in. A vectorising compiler reorders// them, and without exactness the two languages would disagree on rounding.#import "Stdio.xc"#import "include/bench_time.xc"#define N 4096i32 main(i32 argc, u8** argv) { float a[N]; float b[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) { a[i] = (float)((i + seed) % (u32)16); b[i] = (float)((i % (u32)7) + (u32)1); } double acc = 0.0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)400000; r++) for (u32 i = (u32)0; i < (u32)N; i++) acc = acc + (double)(a[i] * b[i]); i64 t1 = bench_now_us(); // (u32)acc directly is UNDEFINED once the sum exceeds u32: at this // iteration count acc reaches ~4.9e10, and the two compilers chose // differently (0 against 4294967295). The double is still exact — well // under 2^53 — so going through u64 and letting the u32 narrowing wrap, // which IS defined, keeps the checksum meaningful and identical. Stdio.printf("%lu %lld\n", (u32)((u64)acc), t1 - t0); return 0; }// float_math — float multiply, accumulated in double. See float_math.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 4096int main(int argc, char **argv) { @autoreleasepool { float a[N], b[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) { a[i] = (float)((i + seed) % 16u); b[i] = (float)((i % 7u) + 1u); } double acc = 0.0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 400000; r++) for (uint32_t i = 0; i < N; i++) acc = acc + (double)(a[i] * b[i]); int64_t t1 = bench_now_us(); // (uint32_t)acc is UNDEFINED once the sum exceeds u32; via uint64_t // the narrowing is defined and wraps. See float_math.xc. printf("%u %lld\n", (uint32_t)(uint64_t)acc, (long long)(t1 - t0)); } return 0; }// float_math — float multiply, accumulated in double. See float_math.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<float, N> a, b; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) { a[i] = static_cast<float>((i + seed) % 16u); b[i] = static_cast<float>((i % 7u) + 1u); } double acc = 0.0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 400000u; r++) for (uint32_t i = 0; i < N; i++) acc = acc + static_cast<double>(a[i] * b[i]); int64_t t1 = bench_now_us(); // Converting acc straight to uint32_t is undefined once the sum exceeds // u32; via uint64_t the narrowing is defined and wraps. See float_math.xc. std::printf("%u %lld\n", static_cast<uint32_t>(static_cast<uint64_t>(acc)), static_cast<long long>(t1 - t0)); return 0; }// float_math — float multiply, accumulated in double. See float_math.xc.
@mainstruct FloatMath { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var a = [Float](repeating: 0, count: n) var b = [Float](repeating: 0, count: n) for i in 0..<n { a[i] = Float((UInt32(i) &+ seed) % 16) b[i] = Float((UInt32(i) % 7) + 1) } var acc = 0.0 let t0 = bench_now_us() for _ in 0..<UInt32(400_000) { for i in 0..<n { acc = acc + Double(a[i] * b[i]) } } let t1 = bench_now_us() // The sum is exact but exceeds UInt32; go through UInt64 and let the // narrowing wrap, as float_math.xc does. print("\(UInt32(truncatingIfNeeded: UInt64(acc))) \(t1 - t0)") }}hash_mix
Section titled “hash_mix”An integer avalanche chain.
Lines: xc 19, Objective-C 22, C++ 20, Swift 17.
// hash_mix — an integer avalanche chain.// Exercises: multiply, shift and xor chains with a serial dependency.#import "Stdio.xc"#import "include/bench_time.xc"i32 main(i32 argc, u8** argv) { u32 h = (u32)argc; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)640000000; r++) { h = h ^ (h >> (u32)16); h = h * (u32)2246822519; h = h ^ (h >> (u32)13); h = h + r; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", h, t1 - t0); return 0; }// hash_mix — an integer avalanche chain. See hash_mix.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>int main(int argc, char **argv) { @autoreleasepool { uint32_t h = (uint32_t)argc; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 640000000; r++) { h = h ^ (h >> 16); h = h * 2246822519u; h = h ^ (h >> 13); h = h + r; } int64_t t1 = bench_now_us(); printf("%u %lld\n", h, (long long)(t1 - t0)); } return 0; }// hash_mix — an integer avalanche chain. See hash_mix.xc.#include <cstdio>#include <cstdint>#include "include/bench_time.h"
int main(int argc, char **argv) { uint32_t h = static_cast<uint32_t>(argc); int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 640000000u; r++) { h = h ^ (h >> 16); h = h * 2246822519u; h = h ^ (h >> 13); h = h + r; } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", h, static_cast<long long>(t1 - t0)); return 0; }// hash_mix — an integer avalanche chain. See hash_mix.xc.
@mainstruct HashMix { static func main() { var h = UInt32(CommandLine.arguments.count) let t0 = bench_now_us() for r in 0..<UInt32(640_000_000) { h = h ^ (h >> 16) h = h &* 2246822519 h = h ^ (h >> 13) h = h &+ r } let t1 = bench_now_us() print("\(h) \(t1 - t0)") }}int_accum
Section titled “int_accum”Integer add and xor over an array.
Lines: xc 29, Objective-C 30, C++ 24, Swift 18.
// int_accum — integer add and xor over an array.// Exercises: loop induction, array addressing, u32 wraparound arithmetic.//// The array is seeded from argc so its contents are unknown at compile time,// and the loop reads it every iteration. Without that both compilers reduce the// whole loop to a closed form and the benchmark measures nothing.#import "Stdio.xc"#import "include/bench_time.xc"
#define N 4096
i32 main(i32 argc, u8** argv) { u32 a[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) a[i] = (i * (u32)2654435761) + seed;
u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)600000; r++) for (u32 i = (u32)0; i < (u32)N; i++) acc = acc + (a[i] ^ acc);
i64 t1 = bench_now_us();
Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// int_accum — integer add and xor over an array. See int_accum.xc.// Same array size, same seed, same loop bounds, same u32 wraparound.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>
#define N 4096
int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed;
uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 600000; r++) for (uint32_t i = 0; i < N; i++) acc = acc + (a[i] ^ acc);
int64_t t1 = bench_now_us();
printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// int_accum — integer add and xor over an array. See int_accum.xc.// Same array size, same seed, same loop bounds, same u32 wraparound.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<uint32_t, N> a; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed;
uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 600000u; r++) for (uint32_t v : a) acc = acc + (v ^ acc); int64_t t1 = bench_now_us();
std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// int_accum — integer add and xor over an array. See int_accum.xc.
@mainstruct IntAccum { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) for i in 0..<n { a[i] = (UInt32(i) &* 2654435761) &+ seed } var acc: UInt32 = 0 let t0 = bench_now_us() for _ in 0..<UInt32(600_000) { for i in 0..<n { acc = acc &+ (a[i] ^ acc) } } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}int_muldiv
Section titled “int_muldiv”Integer multiply and divide by compile-time constants.
Lines: xc 19, Objective-C 21, C++ 21, Swift 18.
// int_muldiv — integer multiply and divide by compile-time constants.// Exercises: strength reduction, magic-number division.#import "Stdio.xc"#import "include/bench_time.xc"#define N 1024i32 main(i32 argc, u8** argv) { u32 a[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) a[i] = (i * (u32)2654435761) + seed; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)6800000; r++) for (u32 i = (u32)0; i < (u32)N; i++) acc = acc + ((a[i] * (u32)7) / (u32)3) + (a[i] / (u32)11); i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// int_muldiv — integer multiply and divide by constants. See int_muldiv.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 1024int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 6800000; r++) for (uint32_t i = 0; i < N; i++) acc = acc + ((a[i] * 7u) / 3u) + (a[i] / 11u); int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// int_muldiv — integer multiply and divide by constants. See int_muldiv.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 1024;
int main(int argc, char **argv) { std::array<uint32_t, N> a; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) a[i] = (i * 2654435761u) + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 6800000u; r++) for (uint32_t v : a) acc = acc + ((v * 7u) / 3u) + (v / 11u); int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// int_muldiv — integer multiply and divide by constants. See int_muldiv.xc.
@mainstruct IntMulDiv { static func main() { let n = 1024 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) for i in 0..<n { a[i] = (UInt32(i) &* 2654435761) &+ seed } var acc: UInt32 = 0 let t0 = bench_now_us() for _ in 0..<UInt32(6_800_000) { for i in 0..<n { acc = acc &+ ((a[i] &* 7) / 3) &+ (a[i] / 11) } } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}matrix_mul
Section titled “matrix_mul”Multiply two small square matrices, repeatedly.
Lines: xc 27, Objective-C 29, C++ 29, Swift 30.
// matrix_mul — multiply two small square matrices, repeatedly.// Exercises: triple-nested loops, strided addressing, accumulation.#import "Stdio.xc"#import "include/bench_time.xc"#define M 32i32 main(i32 argc, u8** argv) { u32 a[M * M]; u32 b[M * M]; u32 c[M * M]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)(M * M); i++) { a[i] = (i + seed) & (u32)15; b[i] = (i ^ seed) & (u32)15; } i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)180000; r++) for (u32 i = (u32)0; i < (u32)M; i++) for (u32 j = (u32)0; j < (u32)M; j++) { u32 s = (u32)0; for (u32 k = (u32)0; k < (u32)M; k++) s = s + (a[i * (u32)M + k] * b[k * (u32)M + j]); c[i * (u32)M + j] = s + r; } u32 acc = (u32)0; for (u32 i = (u32)0; i < (u32)(M * M); i++) acc = acc + c[i]; i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// matrix_mul — multiply two small square matrices, repeatedly. See matrix_mul.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define M 32int main(int argc, char **argv) { @autoreleasepool { uint32_t a[M * M], b[M * M], c[M * M]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < M * M; i++) { a[i] = (i + seed) & 15u; b[i] = (i ^ seed) & 15u; } int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 180000; r++) for (uint32_t i = 0; i < M; i++) for (uint32_t j = 0; j < M; j++) { uint32_t s = 0; for (uint32_t k = 0; k < M; k++) s = s + (a[i * M + k] * b[k * M + j]); c[i * M + j] = s + r; } uint32_t acc = 0; for (uint32_t i = 0; i < M * M; i++) acc = acc + c[i]; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// matrix_mul — multiply two small square matrices, repeatedly. See matrix_mul.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t M = 32;using Matrix = std::array<uint32_t, M * M>;
int main(int argc, char **argv) { Matrix a, b, c; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < M * M; i++) { a[i] = (i + seed) & 15u; b[i] = (i ^ seed) & 15u; } int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 180000u; r++) for (uint32_t i = 0; i < M; i++) for (uint32_t j = 0; j < M; j++) { uint32_t s = 0; for (uint32_t k = 0; k < M; k++) s = s + (a[i * M + k] * b[k * M + j]); c[i * M + j] = s + r; } uint32_t acc = 0; for (uint32_t v : c) acc = acc + v; int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// matrix_mul — multiply two small square matrices, repeatedly. See matrix_mul.xc.
@mainstruct MatrixMul { static func main() { let m = 32 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: m * m) var b = [UInt32](repeating: 0, count: m * m) var c = [UInt32](repeating: 0, count: m * m) for i in 0..<(m * m) { a[i] = (UInt32(i) &+ seed) & 15 b[i] = (UInt32(i) ^ seed) & 15 } let t0 = bench_now_us() for r in 0..<UInt32(180_000) { for i in 0..<m { for j in 0..<m { var s: UInt32 = 0 for k in 0..<m { s = s &+ (a[i * m + k] &* b[k * m + j]) } c[i * m + j] = s &+ r } } } var acc: UInt32 = 0 for i in 0..<(m * m) { acc = acc &+ c[i] } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}matrix_mul_f32
Section titled “matrix_mul_f32”Multiply two float matrices, repeatedly.
Lines: xc 37, Objective-C 35, C++ 33, Swift 32.
// matrix_mul_f32 — multiply two float matrices, repeatedly.// Exercises: a dense float matrix multiply (SME outer products on arm64 macOS// with SME; NEON or SSE elsewhere). The values are small integers, so every// product and sum is exact and the checksum is the same in every language,// whether or not it fuses a*b + s. `:goal(speed)` lets the SME kernel skip// its NaN check of C: only NaN payloads could differ, and there are no NaNs.#import "Stdio.xc"#import "include/bench_time.xc"#define M 128i32 main(i32 argc, u8** argv) { float* a = new float[M * M]; float* b = new float[M * M]; float* c = new float[M * M]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)(M * M); i++) { a[i] = (float)((i + seed) & (u32)15); b[i] = (float)((i ^ seed) & (u32)15); } u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)10000; r++) { a[r % (u32)(M * M)] = (float)(r & (u32)15); for (u32 i = (u32)0; i < (u32)M; i++) :goal(speed) for (u32 j = (u32)0; j < (u32)M; j++) { float s = 0.0; for (u32 k = (u32)0; k < (u32)M; k++) s = s + a[i * (u32)M + k] * b[k * (u32)M + j]; c[i * (u32)M + j] = s; } acc = acc + (u32)c[(r * (u32)7919) % (u32)(M * M)]; } for (u32 i = (u32)0; i < (u32)(M * M); i++) acc = acc + (u32)c[i]; i64 t1 = bench_now_us(); Stdio.printf("%u %lld\n", acc, t1 - t0); return 0; }// matrix_mul_f32 — multiply two float matrices, repeatedly. See matrix_mul_f32.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include <stdlib.h>#include "include/bench_time.h"#include <stdint.h>#define M 128int main(int argc, char **argv) { @autoreleasepool { float *a = malloc(sizeof(float) * M * M), *b = malloc(sizeof(float) * M * M), *c = malloc(sizeof(float) * M * M); uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < M * M; i++) { a[i] = (float)((i + seed) & 15u); b[i] = (float)((i ^ seed) & 15u); } uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 10000; r++) { a[r % (M * M)] = (float)(r & 15u); for (uint32_t i = 0; i < M; i++) for (uint32_t j = 0; j < M; j++) { float s = 0.0f; for (uint32_t k = 0; k < M; k++) s = s + a[i * M + k] * b[k * M + j]; c[i * M + j] = s; } acc = acc + (uint32_t)c[(r * 7919u) % (M * M)]; } for (uint32_t i = 0; i < M * M; i++) acc = acc + (uint32_t)c[i]; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); free(a); free(b); free(c); } return 0; }// matrix_mul_f32 — multiply two float matrices, repeatedly. See matrix_mul_f32.xc.#include <cstdio>#include <cstdint>#include <vector>#include "include/bench_time.h"
constexpr uint32_t M = 128;
int main(int argc, char **argv) { std::vector<float> a(M * M), b(M * M), c(M * M); const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < M * M; i++) { a[i] = static_cast<float>((i + seed) & 15u); b[i] = static_cast<float>((i ^ seed) & 15u); } uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 10000u; r++) { a[r % (M * M)] = static_cast<float>(r & 15u); for (uint32_t i = 0; i < M; i++) for (uint32_t j = 0; j < M; j++) { float s = 0.0f; for (uint32_t k = 0; k < M; k++) s = s + a[i * M + k] * b[k * M + j]; c[i * M + j] = s; } acc = acc + static_cast<uint32_t>(c[(r * 7919u) % (M * M)]); } for (float v : c) acc = acc + static_cast<uint32_t>(v); int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// matrix_mul_f32 — multiply two float matrices, repeatedly. See matrix_mul_f32.xc.
@mainstruct MatrixMulF32 { static func main() { let m = 128 let seed = UInt32(CommandLine.arguments.count) var a = [Float](repeating: 0, count: m * m) var b = [Float](repeating: 0, count: m * m) var c = [Float](repeating: 0, count: m * m) for i in 0..<(m * m) { a[i] = Float((UInt32(i) &+ seed) & 15) b[i] = Float((UInt32(i) ^ seed) & 15) } var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<10000 { a[r % (m * m)] = Float(UInt32(r) & 15) for i in 0..<m { for j in 0..<m { var s: Float = 0 for k in 0..<m { s = s + a[i * m + k] * b[k * m + j] } c[i * m + j] = s } } acc = acc &+ UInt32(c[(r * 7919) % (m * m)]) } for i in 0..<(m * m) { acc = acc &+ UInt32(c[i]) } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}mem_copy
Section titled “mem_copy”Copy between arrays element by element.
Lines: xc 21, Objective-C 24, C++ 24, Swift 20.
// mem_copy — copy between arrays element by element.// Exercises: load/store pairing, the memcpy idiom, pointer induction.#import "Stdio.xc"#import "include/bench_time.xc"#define N 4096i32 main(i32 argc, u8** argv) { u32 src[N]; u32 dst[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) src[i] = i + seed; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)6800000; r++) { for (u32 i = (u32)0; i < (u32)N; i++) dst[i] = src[i] + r; acc = acc + dst[r % (u32)N]; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// mem_copy — copy between arrays element by element. See mem_copy.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 4096int main(int argc, char **argv) { @autoreleasepool { uint32_t src[N], dst[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) src[i] = i + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 6800000; r++) { for (uint32_t i = 0; i < N; i++) dst[i] = src[i] + r; acc = acc + dst[r % N]; } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// mem_copy — copy between arrays element by element. See mem_copy.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 4096;
int main(int argc, char **argv) { std::array<uint32_t, N> src, dst; const uint32_t seed = static_cast<uint32_t>(argc); for (uint32_t i = 0; i < N; i++) src[i] = i + seed; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 6800000u; r++) { for (uint32_t i = 0; i < N; i++) dst[i] = src[i] + r; acc = acc + dst[r % N]; } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// mem_copy — copy between arrays element by element. See mem_copy.xc.
@mainstruct MemCopy { static func main() { let n = 4096 let seed = UInt32(CommandLine.arguments.count) var src = [UInt32](repeating: 0, count: n) var dst = [UInt32](repeating: 0, count: n) for i in 0..<n { src[i] = UInt32(i) &+ seed } var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<UInt32(6_800_000) { for i in 0..<n { dst[i] = src[i] &+ r } acc = acc &+ dst[Int(r % UInt32(n))] } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}method_call
Section titled “method_call”A virtual method call per iteration.
Lines: xc 27, Objective-C 32, C++ 35, Swift 24.
// method_call — a virtual method call per iteration.// Exercises: dispatch cost, inlining across a method boundary.// The class is chosen at run time and score() takes the iteration number, so// the call can be neither resolved at compile time nor hoisted out of the loop.#import "Stdio.xc"#import "include/bench_time.xc"
class Shape { u32 k; void init(u32 x) { k = x; } u32 score(u32 x) { return k ^ x; } }class Boxy : Shape { void init(u32 x) { super.init(x); } u32 score(u32 x) { return (k ^ x) + (u32)1; } }
i32 main(i32 argc, u8** argv) { u32 seed = (u32)argc; Shape* s = new Shape(seed); if ((argc & 1) == 0) s = new Boxy(seed); u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)1600000000; r++) acc = acc + s.score(r); i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// method_call — a virtual method call per iteration. See method_call.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>
@interface Shape : NSObject { @public uint32_t k; }- (instancetype)initWithK:(uint32_t)x;- (uint32_t)score:(uint32_t)x;@end@implementation Shape- (instancetype)initWithK:(uint32_t)x { if ((self = [super init])) k = x; return self; }- (uint32_t)score:(uint32_t)x { return k ^ x; }@end@interface Boxy : Shape @end@implementation Boxy- (uint32_t)score:(uint32_t)x { return (k ^ x) + 1u; }@end
int main(int argc, char **argv) { @autoreleasepool { uint32_t seed = (uint32_t)argc; Shape *s = (argc & 1) ? [[Shape alloc] initWithK:seed] : [[Boxy alloc] initWithK:seed]; uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 1600000000; r++) acc = acc + [s score:r]; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// method_call — a virtual method call per iteration. See method_call.xc.#include <cstdio>#include <cstdint>#include <memory>#include "include/bench_time.h"
class Shape {public: explicit Shape(uint32_t x) : k(x) {} virtual ~Shape() = default; virtual uint32_t score(uint32_t x) const { return k ^ x; }protected: uint32_t k; };
class Boxy : public Shape {public: using Shape::Shape; uint32_t score(uint32_t x) const override { return (k ^ x) + 1u; } };
int main(int argc, char **argv) { const uint32_t seed = static_cast<uint32_t>(argc); std::unique_ptr<Shape> s = (argc & 1) ? std::make_unique<Shape>(seed) : std::make_unique<Boxy>(seed); uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 1600000000u; r++) acc = acc + s->score(r); int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// method_call — a virtual method call per iteration. See method_call.xc.
class Shape { let k: UInt32 init(_ x: UInt32) { k = x } func score(_ x: UInt32) -> UInt32 { k ^ x }}
final class Boxy: Shape { override func score(_ x: UInt32) -> UInt32 { (k ^ x) &+ 1 }}
@mainstruct MethodCall { static func main() { let seed = UInt32(CommandLine.arguments.count) let s: Shape = (CommandLine.arguments.count & 1) != 0 ? Shape(seed) : Boxy(seed) var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<UInt32(1_600_000_000) { acc = acc &+ s.score(r) } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}poly_dispatch
Section titled “poly_dispatch”Dispatch through a mixed array of subclasses.
Lines: xc 25, Objective-C 35, C++ 52, Swift 40.
// poly_dispatch — dispatch through a mixed array of subclasses.// Exercises: virtual dispatch the compiler cannot devirtualise.#import "Stdio.xc"#import "include/bench_time.xc"#define N 256class Op { u32 k; void init(u32 x) { k = x; } u32 apply(u32 v) { return v + k; } }class OpMul : Op { void init(u32 x) { super.init(x); } u32 apply(u32 v) { return v * (k | (u32)1); } }class OpXor : Op { void init(u32 x) { super.init(x); } u32 apply(u32 v) { return v ^ k; } }i32 main(i32 argc, u8** argv) { Op* ops[N]; u32 seed = (u32)argc; for (u32 i = (u32)0; i < (u32)N; i++) { if ((i % (u32)3) == (u32)0) ops[i] = new Op(i + seed); else if ((i % (u32)3) == (u32)1) ops[i] = new OpMul(i + seed); else ops[i] = new OpXor(i + seed); } u32 acc = (u32)1; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)3000000; r++) for (u32 i = (u32)0; i < (u32)N; i++) acc = ops[i].apply(acc) + r; i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// poly_dispatch — dispatch through a mixed array of subclasses. See poly_dispatch.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 256@interface Op : NSObject { @public uint32_t k; }- (instancetype)initWithK:(uint32_t)x; - (uint32_t)apply:(uint32_t)v; @end@implementation Op- (instancetype)initWithK:(uint32_t)x { if ((self = [super init])) k = x; return self; }- (uint32_t)apply:(uint32_t)v { return v + k; } @end@interface OpMul : Op @end@implementation OpMul - (uint32_t)apply:(uint32_t)v { return v * (k | 1u); } @end@interface OpXor : Op @end@implementation OpXor - (uint32_t)apply:(uint32_t)v { return v ^ k; } @end
int main(int argc, char **argv) { @autoreleasepool { Op * __strong ops[N]; uint32_t seed = (uint32_t)argc; for (uint32_t i = 0; i < N; i++) { if ((i % 3u) == 0) ops[i] = [[Op alloc] initWithK:i + seed]; else if ((i % 3u) == 1) ops[i] = [[OpMul alloc] initWithK:i + seed]; else ops[i] = [[OpXor alloc] initWithK:i + seed]; } uint32_t acc = 1; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3000000; r++) for (uint32_t i = 0; i < N; i++) acc = [ops[i] apply:acc] + r; int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// poly_dispatch — dispatch through a mixed array of subclasses. See poly_dispatch.xc.#include <cstdio>#include <cstdint>#include <memory>#include <vector>#include "include/bench_time.h"
constexpr uint32_t N = 256;
class Op {public: explicit Op(uint32_t x) : k(x) {} virtual ~Op() = default; virtual uint32_t apply(uint32_t v) const { return v + k; }protected: uint32_t k; };
class OpMul : public Op {public: using Op::Op; uint32_t apply(uint32_t v) const override { return v * (k | 1u); } };
class OpXor : public Op {public: using Op::Op; uint32_t apply(uint32_t v) const override { return v ^ k; } };
int main(int argc, char **argv) { const uint32_t seed = static_cast<uint32_t>(argc); std::vector<std::unique_ptr<Op>> ops; ops.reserve(N); for (uint32_t i = 0; i < N; i++) { if ((i % 3u) == 0) ops.push_back(std::make_unique<Op>(i + seed)); else if ((i % 3u) == 1) ops.push_back(std::make_unique<OpMul>(i + seed)); else ops.push_back(std::make_unique<OpXor>(i + seed)); } uint32_t acc = 1; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3000000u; r++) for (const auto &op : ops) acc = op->apply(acc) + r; int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// poly_dispatch — dispatch through a mixed array of subclasses. See poly_dispatch.xc.
class Op { let k: UInt32 init(_ x: UInt32) { k = x } func apply(_ v: UInt32) -> UInt32 { v &+ k }}
final class OpMul: Op { override func apply(_ v: UInt32) -> UInt32 { v &* (k | 1) }}
final class OpXor: Op { override func apply(_ v: UInt32) -> UInt32 { v ^ k }}
@mainstruct PolyDispatch { static func main() { let n = 256 let seed = UInt32(CommandLine.arguments.count) var ops: [Op] = [] ops.reserveCapacity(n) for i in 0..<n { let x = UInt32(i) &+ seed switch i % 3 { case 0: ops.append(Op(x)) case 1: ops.append(OpMul(x)) default: ops.append(OpXor(x)) } } var acc: UInt32 = 1 let t0 = bench_now_us() for r in 0..<UInt32(3_000_000) { for i in 0..<n { acc = ops[i].apply(acc) &+ r } } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}Sieve of Eratosthenes over a fixed range, repeatedly.
Lines: xc 25, Objective-C 28, C++ 30, Swift 27.
// sieve — sieve of Eratosthenes over a fixed range, repeatedly.// Exercises: byte array writes, strided inner loops, division-free stepping.#import "Stdio.xc"#import "include/bench_time.xc"#define N 8192i32 main(i32 argc, u8** argv) { u8 flags[N]; u32 seed = (u32)argc; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)150000; r++) { for (u32 i = (u32)0; i < (u32)N; i++) flags[i] = (u8)1; u32 count = (u32)0; for (u32 i = (u32)2; i < (u32)N; i++) if (flags[i] != (u8)0) { count = count + (u32)1; for (u32 j = i + i; j < (u32)N; j = j + i) flags[j] = (u8)0; } acc = acc + count + (r & seed); } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// sieve — sieve of Eratosthenes over a fixed range, repeatedly. See sieve.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 8192int main(int argc, char **argv) { @autoreleasepool { uint8_t flags[N]; uint32_t seed = (uint32_t)argc, acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 150000; r++) { for (uint32_t i = 0; i < N; i++) flags[i] = 1; uint32_t count = 0; for (uint32_t i = 2; i < N; i++) if (flags[i] != 0) { count = count + 1; for (uint32_t j = i + i; j < N; j = j + i) flags[j] = 0; } acc = acc + count + (r & seed); } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// sieve — sieve of Eratosthenes over a fixed range, repeatedly. See sieve.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 8192;
int main(int argc, char **argv) { std::array<uint8_t, N> flags; const uint32_t seed = static_cast<uint32_t>(argc); uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 150000u; r++) { for (uint32_t i = 0; i < N; i++) flags[i] = 1; uint32_t count = 0; for (uint32_t i = 2; i < N; i++) if (flags[i] != 0) { count = count + 1; for (uint32_t j = i + i; j < N; j = j + i) flags[j] = 0; } acc = acc + count + (r & seed); } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// sieve — sieve of Eratosthenes over a fixed range, repeatedly. See sieve.xc.
@mainstruct Sieve { static func main() { let n = 8192 let seed = UInt32(CommandLine.arguments.count) var flags = [UInt8](repeating: 0, count: n) var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<UInt32(150_000) { for i in 0..<n { flags[i] = 1 } var count: UInt32 = 0 for i in 2..<n where flags[i] != 0 { count = count &+ 1 var j = i + i while j < n { flags[j] = 0 j = j + i } } acc = acc &+ count &+ (r & seed) } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}sort_small
Section titled “sort_small”Insertion sort of a small array, repeatedly.
Lines: xc 25, Objective-C 28, C++ 30, Swift 29.
// sort_small — insertion sort of a small array, repeatedly.// Exercises: nested loops, data-dependent branches, swaps.#import "Stdio.xc"#import "include/bench_time.xc"#define N 64i32 main(i32 argc, u8** argv) { u32 a[N]; u32 seed = (u32)argc; u32 acc = (u32)0; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)3200000; r++) { for (u32 i = (u32)0; i < (u32)N; i++) a[i] = ((i * (u32)2654435761) ^ (r * (u32)40503)) + seed; for (u32 i = (u32)1; i < (u32)N; i++) { u32 v = a[i]; u32 j = i; while (j > (u32)0 && a[j - (u32)1] > v) { a[j] = a[j - (u32)1]; j = j - (u32)1; } a[j] = v; } acc = acc + a[0] + a[N - 1]; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// sort_small — insertion sort of a small array, repeatedly. See sort_small.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 64int main(int argc, char **argv) { @autoreleasepool { uint32_t a[N]; uint32_t seed = (uint32_t)argc, acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3200000; r++) { for (uint32_t i = 0; i < N; i++) a[i] = ((i * 2654435761u) ^ (r * 40503u)) + seed; for (uint32_t i = 1; i < N; i++) { uint32_t v = a[i], j = i; while (j > 0 && a[j - 1] > v) { a[j] = a[j - 1]; j = j - 1; } a[j] = v; } acc = acc + a[0] + a[N - 1]; } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// sort_small — insertion sort of a small array, repeatedly. See sort_small.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 64;
int main(int argc, char **argv) { std::array<uint32_t, N> a; const uint32_t seed = static_cast<uint32_t>(argc); uint32_t acc = 0; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3200000u; r++) { for (uint32_t i = 0; i < N; i++) a[i] = ((i * 2654435761u) ^ (r * 40503u)) + seed; for (uint32_t i = 1; i < N; i++) { uint32_t v = a[i], j = i; while (j > 0 && a[j - 1] > v) { a[j] = a[j - 1]; j = j - 1; } a[j] = v; } acc = acc + a.front() + a.back(); } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// sort_small — insertion sort of a small array, repeatedly. See sort_small.xc.
@mainstruct SortSmall { static func main() { let n = 64 let seed = UInt32(CommandLine.arguments.count) var a = [UInt32](repeating: 0, count: n) var acc: UInt32 = 0 let t0 = bench_now_us() for r in 0..<UInt32(3_200_000) { for i in 0..<n { a[i] = ((UInt32(i) &* 2654435761) ^ (r &* 40503)) &+ seed } for i in 1..<n { let v = a[i] var j = i while j > 0 && a[j - 1] > v { a[j] = a[j - 1] j = j - 1 } a[j] = v } acc = acc &+ a[0] &+ a[n - 1] } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}string_scan
Section titled “string_scan”Scan bytes for a delimiter and checksum them.
Lines: xc 22, Objective-C 24, C++ 26, Swift 23.
// string_scan — scan bytes for a delimiter and checksum them.// Exercises: byte loads, comparisons, narrow-type arithmetic.#import "Stdio.xc"#import "include/bench_time.xc"#define N 8192i32 main(i32 argc, u8** argv) { u8 buf[N]; u32 seed = (u32)argc; u32 acc = (u32)0; for (u32 i = (u32)0; i < (u32)N; i++) buf[i] = (u8)(((i * (u32)31) + seed) & (u32)127); i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)3800000; r++) { u32 n = (u32)0; for (u32 i = (u32)0; i < (u32)N; i++) { if (buf[i] == (u8)44) n = n + (u32)1; acc = acc + (u32)buf[i]; } acc = acc + n; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// string_scan — scan bytes for a delimiter and checksum them. See string_scan.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>#define N 8192int main(int argc, char **argv) { @autoreleasepool { uint8_t buf[N]; uint32_t seed = (uint32_t)argc, acc = 0; for (uint32_t i = 0; i < N; i++) buf[i] = (uint8_t)(((i * 31u) + seed) & 127u); int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3800000; r++) { uint32_t n = 0; for (uint32_t i = 0; i < N; i++) { if (buf[i] == 44) n = n + 1; acc = acc + (uint32_t)buf[i]; } acc = acc + n; } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// string_scan — scan bytes for a delimiter and checksum them. See string_scan.xc.#include <array>#include <cstdio>#include <cstdint>#include "include/bench_time.h"
constexpr uint32_t N = 8192;
int main(int argc, char **argv) { std::array<uint8_t, N> buf; const uint32_t seed = static_cast<uint32_t>(argc); uint32_t acc = 0; for (uint32_t i = 0; i < N; i++) buf[i] = static_cast<uint8_t>(((i * 31u) + seed) & 127u); int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 3800000u; r++) { uint32_t n = 0; for (uint8_t c : buf) { if (c == 44) n = n + 1; acc = acc + static_cast<uint32_t>(c); } acc = acc + n; } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// string_scan — scan bytes for a delimiter and checksum them. See string_scan.xc.
@mainstruct StringScan { static func main() { let n = 8192 let seed = UInt32(CommandLine.arguments.count) var buf = [UInt8](repeating: 0, count: n) var acc: UInt32 = 0 for i in 0..<n { buf[i] = UInt8(((UInt32(i) &* 31) &+ seed) & 127) } let t0 = bench_now_us() for _ in 0..<UInt32(3_800_000) { var count: UInt32 = 0 for i in 0..<n { if buf[i] == UInt8(ascii: ",") { count = count &+ 1 } acc = acc &+ UInt32(buf[i]) } acc = acc &+ count } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}struct_copy
Section titled “struct_copy”Pass and return a small struct by value.
Lines: xc 17, Objective-C 19, C++ 23, Swift 26.
// struct_copy — pass and return a small struct by value.// Exercises: struct copy semantics, field layout, argument passing.#import "Stdio.xc"#import "include/bench_time.xc"struct Pt { u32 x, y; }Pt bump(Pt p, u32 d) { Pt q; q.x = p.x + d; q.y = p.y ^ d; return q; }i32 main(i32 argc, u8** argv) { u32 seed = (u32)argc; u32 acc = (u32)0; Pt p; p.x = seed; p.y = seed; i64 t0 = bench_now_us(); for (u32 r = (u32)0; r < (u32)2200000000; r++) { p = bump(p, r); acc = acc + p.x + p.y; } i64 t1 = bench_now_us(); Stdio.printf("%lu %lld\n", acc, t1 - t0); return 0; }// struct_copy — pass and return a small struct by value. See struct_copy.xc.#import <Foundation/Foundation.h>#include <stdio.h>#include "include/bench_time.h"#include <stdint.h>typedef struct { uint32_t x, y; } Pt;static Pt bump(Pt p, uint32_t d) { Pt q; q.x = p.x + d; q.y = p.y ^ d; return q; }int main(int argc, char **argv) { @autoreleasepool { uint32_t seed = (uint32_t)argc, acc = 0; Pt p; p.x = seed; p.y = seed; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 2200000000; r++) { p = bump(p, r); acc = acc + p.x + p.y; } int64_t t1 = bench_now_us(); printf("%u %lld\n", acc, (long long)(t1 - t0)); } return 0; }// struct_copy — pass and return a small struct by value. See struct_copy.xc.#include <cstdio>#include <cstdint>#include "include/bench_time.h"
struct Pt { uint32_t x, y; };
namespace { Pt bump(Pt p, uint32_t d) { return Pt{p.x + d, p.y ^ d}; } }
int main(int argc, char **argv) { const uint32_t seed = static_cast<uint32_t>(argc); uint32_t acc = 0; Pt p{seed, seed}; int64_t t0 = bench_now_us(); for (uint32_t r = 0; r < 2200000000u; r++) { p = bump(p, r); acc = acc + p.x + p.y; } int64_t t1 = bench_now_us(); std::printf("%u %lld\n", acc, static_cast<long long>(t1 - t0)); return 0; }// struct_copy — pass and return a small struct by value. See struct_copy.xc.
struct Pt { var x: UInt32 var y: UInt32}
func bump(_ p: Pt, _ d: UInt32) -> Pt { Pt(x: p.x &+ d, y: p.y ^ d)}
@mainstruct StructCopy { static func main() { let seed = UInt32(CommandLine.arguments.count) var acc: UInt32 = 0 var p = Pt(x: seed, y: seed) let t0 = bench_now_us() for r in 0..<UInt32(2_200_000_000) { p = bump(p, r) acc = acc &+ p.x &+ p.y } let t1 = bench_now_us() print("\(acc) \(t1 - t0)") }}Parallel blocks and the GPU
Section titled “Parallel blocks and the GPU”The programs behind the performance page’s GPU tables. Each is one xc program:
its work is a par block, a loop whose iterations are independent, and the
runtime runs it on the CPU’s threads or on the GPU (Metal on a Mac, CUDA or
Vulkan on Windows, Vulkan on Linux and Android, WebGPU in the browser),
whichever it finds faster. Nothing in the source
names a device, a kernel language or a thread.
mandel_deep
Section titled “mandel_deep”A deliberately compute-heavy escape-time iteration that also
Lines: 66.
// mandel_deep — a deliberately compute-heavy escape-time iteration that also// writes its result, so a second GPU can help. Two things have to be true for// the tile queue to spread a block across cards: the block must be big enough// that the per-tile cost is amortised (the light `mandelbrot` finishes in about// a millisecond, far too little), and it must move per-item data, because the// queue cuts tiles by the bytes they move — a block that only reduces to a// scalar is one tile, and one tile runs on one GPU. So this renders a 4096x4096// iteration-count image (a 64 MB array it writes) at up to 4096 iterations,// over a window biased onto the set's body so most pixels run the full count:// uniform, heavy work with data to tile. One item per pixel. Prints// "<checksum> <best_us> <first_us>"; the checksum is the total iteration count// to the nearest million, coarse enough that fast GPU maths cannot move it.#import "Stdio.xc"#import "Par.xc"#import "../src/include/bench_time.xc"
#ifndef W#define W 4096#endif#ifndef MAXITER#define MAXITER 4096#endif#define SIZE (W * W)
u32 counts[SIZE];
i32 main(void){ u32 total = (u32)0; i64 best = (i64)0; i64 first = (i64)0; for (u32 rep in 0..8) { total = (u32)0; i64 t0 = bench_now_us(); par mandel :reduce(+ total) { for (u32 i in 0..SIZE) { // The window [-1.5, 0.0] x [-0.75, 0.75]: most pixels are inside // the set and so run the full iteration count. float cx = (float)(i % (u32)W) * (1.5f / (float)W) - 1.5f; float cy = (float)(i / (u32)W) * (1.5f / (float)W) - 0.75f; float x = 0.0f; float y = 0.0f; u32 k = (u32)0; while (k < (u32)MAXITER && x * x + y * y < 4.0f) { float xt = x * x - y * y + cx; y = 2.0f * x * y + cy; x = xt; k = k + (u32)1; } counts[i] = k; total = total + k; } } i64 t1 = bench_now_us(); if (rep == (u32)0) first = t1 - t0; if (rep == (u32)0 || t1 - t0 < best) best = t1 - t0; } Stdio.printf("%u %lld %lld\n", (total + (u32)500000) / (u32)1000000, best, first); return 0;}mandelbrot
Section titled “mandelbrot”Escape-time iteration counts over a 2048x2048 grid, one item
Lines: 51.
// mandelbrot — escape-time iteration counts over a 2048x2048 grid, one item// per pixel: arithmetic only, with a data-dependent loop. Prints// "<checksum> <best_us> <first_us>"; the checksum is the total iteration count to// the nearest million, coarse enough that fast GPU maths cannot move it.#import "Stdio.xc"#import "Par.xc"#import "../src/include/bench_time.xc"
#define W 2048#define SIZE (2048 * 2048)
i32 main(void){ // Eight runs: the first carries one-off costs (a GPU builds its kernel // then), so the figure is the best run; the first is printed too. Auto // decides after its fourth (two on each device), and uses the rest. u32 total = (u32)0; i64 best = (i64)0; i64 first = (i64)0; for (u32 rep in 0..8) { total = (u32)0; i64 t0 = bench_now_us(); par mandel :reduce(+ total) { for (u32 i in 0..SIZE) { float cx = (float)(i % (u32)W) * (3.0f / 2048.0f) - 2.0f; float cy = (float)(i / (u32)W) * (3.0f / 2048.0f) - 1.5f; float x = 0.0f; float y = 0.0f; u32 k = (u32)0; while (k < (u32)256 && x * x + y * y < 4.0f) { float xt = x * x - y * y + cx; y = 2.0f * x * y + cy; x = xt; k = k + (u32)1; } total = total + k; } } i64 t1 = bench_now_us(); if (rep == (u32)0) first = t1 - t0; if (rep == (u32)0 || t1 - t0 < best) best = t1 - t0; } Stdio.printf("%u %lld %lld\n", (total + (u32)500000) / (u32)1000000, best, first); return 0;}Hashcat’s MD5 workload in xc. Each item hashes one 8-byte candidate
Lines: 306.
// md5 — hashcat's MD5 workload in xc. Each item hashes one 8-byte candidate// through the full MD5 compression (64 rounds over a single 512-bit block),// the way hashcat's -m 0 kernel does: short candidates, one block, raw// throughput measured in hashes per second.//// The hash runs inside a `par` block, so the device is the compiler's to// choose: XC_PAR=cpu one CPU thread or all of them, XC_PAR=gpu the GPU// (Metal, Vulkan or CUDA), the default auto measures both and keeps the// faster. `md5 -t` runs the RFC 1321 known-answer tests on the CPU; the same// MD5_ROUNDS drives the test and the kernel, so passing the test proves the// code the GPU runs.//// The benchmark prints "<checksum> <best_us> <first_us>": the sum of every// candidate's four digest words (exact, so every mode must agree), the best// of eight runs, and the first run (which carries the one-off cost of a GPU// building its kernel). A hashes-per-second line goes to stderr.#import "Stdio.xc"#import "Par.xc"
// Headline numbers go to stderr, so stdout stays the three-token line the par// benchmarks share. write() is the libSystem call Stdio itself uses. The// wasm32 browser build reads stdout only, so it leaves all this out.#if !ARCH_wasm32i32 write(i32 fd, u8* buf, i32 len);
void hc_estr(u8* s) { i32 n = 0; while (s[n] != 0) n = n + 1; write(2, s, n); }
void hc_eu64(u64 v) { u8 buf[24]; i32 start = 24; if (v == 0) { start = start - 1; buf[start] = '0'; } while (v > 0) { start = start - 1; buf[start] = (u8)('0' + (v % 10)); v = v / 10; } write(2, &buf[start], 24 - start); }#endif
// A monotonic microsecond clock, measured the same way on every target.#if ARCH_win64i32 QueryPerformanceCounter(i64* count);i32 QueryPerformanceFrequency(i64* freq);#elsei32 clock_gettime(i32 clk, pointer ts); // the library's signature (ParDevice.xc, RunLoop.xc)#endif
struct HcSpec { i64 sec; i64 nsec; }
i64 hc_now_us(void) {#if ARCH_win64 i64 c = 0; i64 f = 1; QueryPerformanceCounter(&c); QueryPerformanceFrequency(&f); return (c / f) * 1000000 + ((c % f) * 1000000) / f;#else HcSpec ts;#if ARCH_x86_64 clock_gettime(1, (pointer)&ts);#else clock_gettime(6, (pointer)&ts);#endif return (ts.sec * 1000000) + (ts.nsec / 1000);#endif }
// Candidates per par launch: large enough to saturate a GPU (at 16M the run// is too short and launch overhead dominates; 64M reaches within a few// percent of the peak). Each is one full MD5 compression. A serial CPU run// of this size takes a few seconds per repetition; the default auto run// picks the GPU and stays fast.#define BATCH (64 * 1024 * 1024)
// The 32-bit word packed into bytes 4..7 of every candidate, so the input// looks like an 8-byte password that varies only in its first word.#define TAG 0x6b636168
// MD5 primitives: left rotate and the four round functions.#define ROTL(x, c) (((x) << (c)) | ((x) >> (32 - (c))))#define MD5F(x, y, z) (((x) & (y)) | (~(x) & (z)))#define MD5G(x, y, z) (((x) & (z)) | ((y) & ~(z)))#define MD5H(x, y, z) ((x) ^ (y) ^ (z))#define MD5I(x, y, z) ((y) ^ ((x) | ~(z)))
// One step: the updated word becomes b + ((a + f(b,c,d) + m + k) rotl s).#define FF(a, b, c, d, m, s, k) a = b + ROTL(a + MD5F(b, c, d) + (m) + (k), s)#define GG(a, b, c, d, m, s, k) a = b + ROTL(a + MD5G(b, c, d) + (m) + (k), s)#define HH(a, b, c, d, m, s, k) a = b + ROTL(a + MD5H(b, c, d) + (m) + (k), s)#define II(a, b, c, d, m, s, k) a = b + ROTL(a + MD5I(b, c, d) + (m) + (k), s)
// The 64 steps over the message words m0..m15, mutating the working words// a,b,c,d. The caller sets a,b,c,d to the IV first and adds the IV back// afterwards.#define MD5_ROUNDS \ FF(a, b, c, d, m0, 7, 0xd76aa478); \ FF(d, a, b, c, m1, 12, 0xe8c7b756); \ FF(c, d, a, b, m2, 17, 0x242070db); \ FF(b, c, d, a, m3, 22, 0xc1bdceee); \ FF(a, b, c, d, m4, 7, 0xf57c0faf); \ FF(d, a, b, c, m5, 12, 0x4787c62a); \ FF(c, d, a, b, m6, 17, 0xa8304613); \ FF(b, c, d, a, m7, 22, 0xfd469501); \ FF(a, b, c, d, m8, 7, 0x698098d8); \ FF(d, a, b, c, m9, 12, 0x8b44f7af); \ FF(c, d, a, b, m10, 17, 0xffff5bb1); \ FF(b, c, d, a, m11, 22, 0x895cd7be); \ FF(a, b, c, d, m12, 7, 0x6b901122); \ FF(d, a, b, c, m13, 12, 0xfd987193); \ FF(c, d, a, b, m14, 17, 0xa679438e); \ FF(b, c, d, a, m15, 22, 0x49b40821); \ GG(a, b, c, d, m1, 5, 0xf61e2562); \ GG(d, a, b, c, m6, 9, 0xc040b340); \ GG(c, d, a, b, m11, 14, 0x265e5a51); \ GG(b, c, d, a, m0, 20, 0xe9b6c7aa); \ GG(a, b, c, d, m5, 5, 0xd62f105d); \ GG(d, a, b, c, m10, 9, 0x02441453); \ GG(c, d, a, b, m15, 14, 0xd8a1e681); \ GG(b, c, d, a, m4, 20, 0xe7d3fbc8); \ GG(a, b, c, d, m9, 5, 0x21e1cde6); \ GG(d, a, b, c, m14, 9, 0xc33707d6); \ GG(c, d, a, b, m3, 14, 0xf4d50d87); \ GG(b, c, d, a, m8, 20, 0x455a14ed); \ GG(a, b, c, d, m13, 5, 0xa9e3e905); \ GG(d, a, b, c, m2, 9, 0xfcefa3f8); \ GG(c, d, a, b, m7, 14, 0x676f02d9); \ GG(b, c, d, a, m12, 20, 0x8d2a4c8a); \ HH(a, b, c, d, m5, 4, 0xfffa3942); \ HH(d, a, b, c, m8, 11, 0x8771f681); \ HH(c, d, a, b, m11, 16, 0x6d9d6122); \ HH(b, c, d, a, m14, 23, 0xfde5380c); \ HH(a, b, c, d, m1, 4, 0xa4beea44); \ HH(d, a, b, c, m4, 11, 0x4bdecfa9); \ HH(c, d, a, b, m7, 16, 0xf6bb4b60); \ HH(b, c, d, a, m10, 23, 0xbebfbc70); \ HH(a, b, c, d, m13, 4, 0x289b7ec6); \ HH(d, a, b, c, m0, 11, 0xeaa127fa); \ HH(c, d, a, b, m3, 16, 0xd4ef3085); \ HH(b, c, d, a, m6, 23, 0x04881d05); \ HH(a, b, c, d, m9, 4, 0xd9d4d039); \ HH(d, a, b, c, m12, 11, 0xe6db99e5); \ HH(c, d, a, b, m15, 16, 0x1fa27cf8); \ HH(b, c, d, a, m2, 23, 0xc4ac5665); \ II(a, b, c, d, m0, 6, 0xf4292244); \ II(d, a, b, c, m7, 10, 0x432aff97); \ II(c, d, a, b, m14, 15, 0xab9423a7); \ II(b, c, d, a, m5, 21, 0xfc93a039); \ II(a, b, c, d, m12, 6, 0x655b59c3); \ II(d, a, b, c, m3, 10, 0x8f0ccc92); \ II(c, d, a, b, m10, 15, 0xffeff47d); \ II(b, c, d, a, m1, 21, 0x85845dd1); \ II(a, b, c, d, m8, 6, 0x6fa87e4f); \ II(d, a, b, c, m15, 10, 0xfe2ce6e0); \ II(c, d, a, b, m6, 15, 0xa3014314); \ II(b, c, d, a, m13, 21, 0x4e0811a1); \ II(a, b, c, d, m4, 6, 0xf7537e82); \ II(d, a, b, c, m11, 10, 0xbd3af235); \ II(c, d, a, b, m2, 15, 0x2ad7d2bb); \ II(b, c, d, a, m9, 21, 0xeb86d391)
// MD5 of a message shorter than 56 bytes (one padded block), on the CPU.// Writes the 32 hex digits of the digest through `out` (no NUL).void md5_hex(u8* msg, u32 len, u8* out) { u32 w[16]; for (u32 i in 0..16) w[i] = 0; for (u32 i in 0..len) w[i >> 2] = w[i >> 2] | ((u32)msg[i] << ((i & 3) * 8)); w[len >> 2] = w[len >> 2] | ((u32)0x80 << ((len & 3) * 8)); w[14] = len * 8;
u32 m0 = w[0]; u32 m1 = w[1]; u32 m2 = w[2]; u32 m3 = w[3]; u32 m4 = w[4]; u32 m5 = w[5]; u32 m6 = w[6]; u32 m7 = w[7]; u32 m8 = w[8]; u32 m9 = w[9]; u32 m10 = w[10]; u32 m11 = w[11]; u32 m12 = w[12]; u32 m13 = w[13]; u32 m14 = w[14]; u32 m15 = w[15];
u32 a = 0x67452301; u32 b = 0xefcdab89; u32 c = 0x98badcfe; u32 d = 0x10325476; MD5_ROUNDS; a = a + 0x67452301; b = b + 0xefcdab89; c = c + 0x98badcfe; d = d + 0x10325476;
u32 word[4]; word[0] = a; word[1] = b; word[2] = c; word[3] = d; u8* hexd = "0123456789abcdef"; u32 o = 0; for (u32 wi in 0..4) for (u32 bi in 0..4) { u32 byte = (word[wi] >> (bi * 8)) & 0xff; out[o] = hexd[byte >> 4]; out[o + 1] = hexd[byte & 0xf]; o = o + 2; } }
i32 selftest(void) { u8 got[33]; got[32] = 0; u8* msgs[4]; msgs[0] = ""; msgs[1] = "abc"; msgs[2] = "message digest"; msgs[3] = "abcdefghijklmnopqrstuvwxyz"; u32 lens[4]; lens[0] = 0; lens[1] = 3; lens[2] = 14; lens[3] = 26; u8* want[4]; want[0] = "d41d8cd98f00b204e9800998ecf8427e"; want[1] = "900150983cd24fb0d6963f7d28e17f72"; want[2] = "f96b697d7cb7938d525a2f31aaf161d0"; want[3] = "c3fcd3d76192e4007dfb496cca67e13b";
i32 fails = 0; for (u32 i in 0..4) { md5_hex(msgs[i], lens[i], got); bool ok = true; for (u32 j in 0..32) if (got[j] != want[i][j]) ok = false; Stdio.printf("md5(\"%s\") = %s %s\n", msgs[i], (u8*)got, ok ? "ok" : "FAIL"); if (!ok) fails = fails + 1; } return fails; }
i32 main(i32 argc, u8** argv) { if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 't') return selftest() == 0 ? 0 : 1;
u32 total = 0; i64 best = 0; i64 first = 0; for (u32 rep in 0..8) { total = 0; i64 t0 = hc_now_us(); par md5 :reduce(+ total) { for (u32 i in 0..BATCH) { // The candidate: word 0 is the item index, word 1 the tag, // then MD5 padding for an 8-byte message (0x80, then zeros, // then the 64-bit length). u32 m0 = i; u32 m1 = TAG; u32 m2 = 0x80; u32 m3 = 0; u32 m4 = 0; u32 m5 = 0; u32 m6 = 0; u32 m7 = 0; u32 m8 = 0; u32 m9 = 0; u32 m10 = 0; u32 m11 = 0; u32 m12 = 0; u32 m13 = 0; u32 m14 = 64; u32 m15 = 0;
u32 a = 0x67452301; u32 b = 0xefcdab89; u32 c = 0x98badcfe; u32 d = 0x10325476; MD5_ROUNDS; total = total + (a + 0x67452301) + (b + 0xefcdab89) + (c + 0x98badcfe) + (d + 0x10325476); } } i64 t1 = hc_now_us(); if (rep == 0) first = t1 - t0; if (rep == 0 || t1 - t0 < best) best = t1 - t0; }
Stdio.printf("%u %lld %lld\n", total, best, first); // Hashes per second from the best run, exact integer division. Skipped on // wasm32, where the browser harness reads stdout and there is no stderr.#if !ARCH_wasm32 u64 hs = best > 0 ? ((u64)BATCH * 1000000) / (u64)best : 0; hc_estr("md5: "); hc_eu64(hs); hc_estr(" H/s (best run "); hc_eu64((u64)best); hc_estr(" us for "); hc_eu64((u64)BATCH); hc_estr(" candidates)\n");#endif return 0; }md5_mask
Section titled “md5_mask”Hashcat’s MASK-ATTACK MD5 workload in xc. Unlike md5.xc (which
Lines: 298.
// md5_mask — hashcat's MASK-ATTACK MD5 workload in xc. Unlike md5.xc (which// hashes one short candidate per item), this mirrors `-a 3`: a fixed prefix// and one sweeping mask position. The MD5 steps before the mask word are// loop-invariant, so they run once per prefix, not once per candidate -- the// optimisation behind hashcat's faster optimized kernel.//// Each candidate is 52 bytes: m0..m11 the prefix (m0 = the outer index, the// rest fixed), m12 the mask word (the inner sweep), then MD5 padding. The par// block runs the outer loop over prefixes; for each it computes steps 0..11// once, then the inner loop runs steps 12..63 per candidate. `md5_mask -t`// checks one candidate's full digest against a known value.#import "Stdio.xc"#import "Par.xc"
i32 write(i32 fd, u8* buf, i32 len);
void hc_estr(u8* s) { i32 n = 0; while (s[n] != 0) n = n + 1; write(2, s, n); }void hc_eu64(u64 v) { u8 buf[24]; i32 start = 24; if (v == 0) { start = start - 1; buf[start] = '0'; } while (v > 0) { start = start - 1; buf[start] = (u8)('0' + (v % 10)); v = v / 10; } write(2, &buf[start], 24 - start); }
#if ARCH_win64i32 QueryPerformanceCounter(i64* count);i32 QueryPerformanceFrequency(i64* freq);#elsei32 clock_gettime(i32 clk, pointer ts); // the library's signature (ParDevice.xc, RunLoop.xc)#endifstruct HcSpec { i64 sec; i64 nsec; }i64 hc_now_us(void) {#if ARCH_win64 i64 c = 0; i64 f = 1; QueryPerformanceCounter(&c); QueryPerformanceFrequency(&f); return (c / f) * 1000000 + ((c % f) * 1000000) / f;#else HcSpec ts;#if ARCH_x86_64 clock_gettime(1, (pointer)&ts);#else clock_gettime(6, (pointer)&ts);#endif return (ts.sec * 1000000) + (ts.nsec / 1000);#endif }
// Prefixes per launch, and mask values per prefix. Total candidates is their// product; each is one 52-byte MD5.#define OUTER (1024 * 1024)#define INNER 64
// The 11 fixed prefix words m1..m11 (m0 is the outer index, m12 the mask).#define P1 0x6b636168#define P2 0x74736168#define P3 0x6d786378#define P4 0x31323334#define P5 0x35363738#define P6 0x61626364#define P7 0x65666768#define P8 0x696a6b6c#define P9 0x6d6e6f70#define P10 0x71727374#define P11 0x75767778
// MD5 primitives (as md5.xc).#define ROTL(x, c) (((x) << (c)) | ((x) >> (32 - (c))))#define MD5F(x, y, z) ((z) ^ ((x) & ((y) ^ (z))))#define MD5G(x, y, z) ((y) ^ ((z) & ((x) ^ (y))))#define MD5H(x, y, z) ((x) ^ (y) ^ (z))#define MD5I(x, y, z) ((y) ^ ((x) | ~(z)))#define FF(a, b, c, d, m, s, k) a = b + ROTL(a + MD5F(b, c, d) + (m) + (k), s)#define GG(a, b, c, d, m, s, k) a = b + ROTL(a + MD5G(b, c, d) + (m) + (k), s)#define HH(a, b, c, d, m, s, k) a = b + ROTL(a + MD5H(b, c, d) + (m) + (k), s)#define II(a, b, c, d, m, s, k) a = b + ROTL(a + MD5I(b, c, d) + (m) + (k), s)
#define MD5_PREFIX(a, b, c, d) \ FF(a, b, c, d, m0, 7, 0xd76aa478); \ FF(d, a, b, c, m1, 12, 0xe8c7b756); \ FF(c, d, a, b, m2, 17, 0x242070db); \ FF(b, c, d, a, m3, 22, 0xc1bdceee); \ FF(a, b, c, d, m4, 7, 0xf57c0faf); \ FF(d, a, b, c, m5, 12, 0x4787c62a); \ FF(c, d, a, b, m6, 17, 0xa8304613); \ FF(b, c, d, a, m7, 22, 0xfd469501); \ FF(a, b, c, d, m8, 7, 0x698098d8); \ FF(d, a, b, c, m9, 12, 0x8b44f7af); \ FF(c, d, a, b, m10, 17, 0xffff5bb1); \ FF(b, c, d, a, m11, 22, 0x895cd7be)
#define MD5_REST(a, b, c, d) \ FF(a, b, c, d, m12, 7, 0x6b901122); \ FF(d, a, b, c, m13, 12, 0xfd987193); \ FF(c, d, a, b, m14, 17, 0xa679438e); \ FF(b, c, d, a, m15, 22, 0x49b40821); \ GG(a, b, c, d, m1, 5, 0xf61e2562); \ GG(d, a, b, c, m6, 9, 0xc040b340); \ GG(c, d, a, b, m11, 14, 0x265e5a51); \ GG(b, c, d, a, m0, 20, 0xe9b6c7aa); \ GG(a, b, c, d, m5, 5, 0xd62f105d); \ GG(d, a, b, c, m10, 9, 0x02441453); \ GG(c, d, a, b, m15, 14, 0xd8a1e681); \ GG(b, c, d, a, m4, 20, 0xe7d3fbc8); \ GG(a, b, c, d, m9, 5, 0x21e1cde6); \ GG(d, a, b, c, m14, 9, 0xc33707d6); \ GG(c, d, a, b, m3, 14, 0xf4d50d87); \ GG(b, c, d, a, m8, 20, 0x455a14ed); \ GG(a, b, c, d, m13, 5, 0xa9e3e905); \ GG(d, a, b, c, m2, 9, 0xfcefa3f8); \ GG(c, d, a, b, m7, 14, 0x676f02d9); \ GG(b, c, d, a, m12, 20, 0x8d2a4c8a); \ HH(a, b, c, d, m5, 4, 0xfffa3942); \ HH(d, a, b, c, m8, 11, 0x8771f681); \ HH(c, d, a, b, m11, 16, 0x6d9d6122); \ HH(b, c, d, a, m14, 23, 0xfde5380c); \ HH(a, b, c, d, m1, 4, 0xa4beea44); \ HH(d, a, b, c, m4, 11, 0x4bdecfa9); \ HH(c, d, a, b, m7, 16, 0xf6bb4b60); \ HH(b, c, d, a, m10, 23, 0xbebfbc70); \ HH(a, b, c, d, m13, 4, 0x289b7ec6); \ HH(d, a, b, c, m0, 11, 0xeaa127fa); \ HH(c, d, a, b, m3, 16, 0xd4ef3085); \ HH(b, c, d, a, m6, 23, 0x04881d05); \ HH(a, b, c, d, m9, 4, 0xd9d4d039); \ HH(d, a, b, c, m12, 11, 0xe6db99e5); \ HH(c, d, a, b, m15, 16, 0x1fa27cf8); \ HH(b, c, d, a, m2, 23, 0xc4ac5665); \ II(a, b, c, d, m0, 6, 0xf4292244); \ II(d, a, b, c, m7, 10, 0x432aff97); \ II(c, d, a, b, m14, 15, 0xab9423a7); \ II(b, c, d, a, m5, 21, 0xfc93a039); \ II(a, b, c, d, m12, 6, 0x655b59c3); \ II(d, a, b, c, m3, 10, 0x8f0ccc92); \ II(c, d, a, b, m10, 15, 0xffeff47d); \ II(b, c, d, a, m1, 21, 0x85845dd1); \ II(a, b, c, d, m8, 6, 0x6fa87e4f); \ II(d, a, b, c, m15, 10, 0xfe2ce6e0); \ II(c, d, a, b, m6, 15, 0xa3014314); \ II(b, c, d, a, m13, 21, 0x4e0811a1); \ II(a, b, c, d, m4, 6, 0xf7537e82); \ II(d, a, b, c, m11, 10, 0xbd3af235); \ II(c, d, a, b, m2, 15, 0x2ad7d2bb); \ II(b, c, d, a, m9, 21, 0xeb86d391)
#define MD5_FULL(a, b, c, d) \ FF(a, b, c, d, m0, 7, 0xd76aa478); \ FF(d, a, b, c, m1, 12, 0xe8c7b756); \ FF(c, d, a, b, m2, 17, 0x242070db); \ FF(b, c, d, a, m3, 22, 0xc1bdceee); \ FF(a, b, c, d, m4, 7, 0xf57c0faf); \ FF(d, a, b, c, m5, 12, 0x4787c62a); \ FF(c, d, a, b, m6, 17, 0xa8304613); \ FF(b, c, d, a, m7, 22, 0xfd469501); \ FF(a, b, c, d, m8, 7, 0x698098d8); \ FF(d, a, b, c, m9, 12, 0x8b44f7af); \ FF(c, d, a, b, m10, 17, 0xffff5bb1); \ FF(b, c, d, a, m11, 22, 0x895cd7be); \ FF(a, b, c, d, m12, 7, 0x6b901122); \ FF(d, a, b, c, m13, 12, 0xfd987193); \ FF(c, d, a, b, m14, 17, 0xa679438e); \ FF(b, c, d, a, m15, 22, 0x49b40821); \ GG(a, b, c, d, m1, 5, 0xf61e2562); \ GG(d, a, b, c, m6, 9, 0xc040b340); \ GG(c, d, a, b, m11, 14, 0x265e5a51); \ GG(b, c, d, a, m0, 20, 0xe9b6c7aa); \ GG(a, b, c, d, m5, 5, 0xd62f105d); \ GG(d, a, b, c, m10, 9, 0x02441453); \ GG(c, d, a, b, m15, 14, 0xd8a1e681); \ GG(b, c, d, a, m4, 20, 0xe7d3fbc8); \ GG(a, b, c, d, m9, 5, 0x21e1cde6); \ GG(d, a, b, c, m14, 9, 0xc33707d6); \ GG(c, d, a, b, m3, 14, 0xf4d50d87); \ GG(b, c, d, a, m8, 20, 0x455a14ed); \ GG(a, b, c, d, m13, 5, 0xa9e3e905); \ GG(d, a, b, c, m2, 9, 0xfcefa3f8); \ GG(c, d, a, b, m7, 14, 0x676f02d9); \ GG(b, c, d, a, m12, 20, 0x8d2a4c8a); \ HH(a, b, c, d, m5, 4, 0xfffa3942); \ HH(d, a, b, c, m8, 11, 0x8771f681); \ HH(c, d, a, b, m11, 16, 0x6d9d6122); \ HH(b, c, d, a, m14, 23, 0xfde5380c); \ HH(a, b, c, d, m1, 4, 0xa4beea44); \ HH(d, a, b, c, m4, 11, 0x4bdecfa9); \ HH(c, d, a, b, m7, 16, 0xf6bb4b60); \ HH(b, c, d, a, m10, 23, 0xbebfbc70); \ HH(a, b, c, d, m13, 4, 0x289b7ec6); \ HH(d, a, b, c, m0, 11, 0xeaa127fa); \ HH(c, d, a, b, m3, 16, 0xd4ef3085); \ HH(b, c, d, a, m6, 23, 0x04881d05); \ HH(a, b, c, d, m9, 4, 0xd9d4d039); \ HH(d, a, b, c, m12, 11, 0xe6db99e5); \ HH(c, d, a, b, m15, 16, 0x1fa27cf8); \ HH(b, c, d, a, m2, 23, 0xc4ac5665); \ II(a, b, c, d, m0, 6, 0xf4292244); \ II(d, a, b, c, m7, 10, 0x432aff97); \ II(c, d, a, b, m14, 15, 0xab9423a7); \ II(b, c, d, a, m5, 21, 0xfc93a039); \ II(a, b, c, d, m12, 6, 0x655b59c3); \ II(d, a, b, c, m3, 10, 0x8f0ccc92); \ II(c, d, a, b, m10, 15, 0xffeff47d); \ II(b, c, d, a, m1, 21, 0x85845dd1); \ II(a, b, c, d, m8, 6, 0x6fa87e4f); \ II(d, a, b, c, m15, 10, 0xfe2ce6e0); \ II(c, d, a, b, m6, 15, 0xa3014314); \ II(b, c, d, a, m13, 21, 0x4e0811a1); \ II(a, b, c, d, m4, 6, 0xf7537e82); \ II(d, a, b, c, m11, 10, 0xbd3af235); \ II(c, d, a, b, m2, 15, 0x2ad7d2bb); \ II(b, c, d, a, m9, 21, 0xeb86d391)
// Full MD5 of the candidate (p, mask), 52 bytes, on the CPU, as 32 hex digits.void md5_mask_hex(u32 p, u32 mask, u8* out) { u32 m0 = p; u32 m1 = P1; u32 m2 = P2; u32 m3 = P3; u32 m4 = P4; u32 m5 = P5; u32 m6 = P6; u32 m7 = P7; u32 m8 = P8; u32 m9 = P9; u32 m10 = P10; u32 m11 = P11; u32 m12 = mask; u32 m13 = 0x80; u32 m14 = 416; u32 m15 = 0; u32 a = 0x67452301; u32 b = 0xefcdab89; u32 c = 0x98badcfe; u32 d = 0x10325476; MD5_FULL(a, b, c, d); a = a + 0x67452301; b = b + 0xefcdab89; c = c + 0x98badcfe; d = d + 0x10325476; u32 word[4]; word[0] = a; word[1] = b; word[2] = c; word[3] = d; u8* hexd = "0123456789abcdef"; u32 o = 0; for (u32 wi in 0..4) for (u32 bi in 0..4) { u32 byte = (word[wi] >> (bi * 8)) & 0xff; out[o] = hexd[byte >> 4]; out[o + 1] = hexd[byte & 0xf]; o = o + 2; } }
i32 selftest(void) { u8 got[33]; got[32] = 0; md5_mask_hex(0, 0, got); u8* want = "8ac92423f2a00548a06836464f84346e"; bool ok = true; for (u32 j in 0..32) if (got[j] != want[j]) ok = false; Stdio.printf("md5_mask(p=0,mask=0) = %s %s\n", (u8*)got, ok ? "ok" : "FAIL"); return ok ? 0 : 1; }
i32 main(i32 argc, u8** argv) { if (argc > 1 && argv[1][0] == '-' && argv[1][1] == 't') return selftest();
u32 total = 0; i64 best = 0; i64 first = 0; for (u32 rep in 0..8) { total = 0; i64 t0 = hc_now_us(); par md5mask :reduce(+ total) { for (u32 p in 0..OUTER) { u32 m0 = p; u32 m1 = P1; u32 m2 = P2; u32 m3 = P3; u32 m4 = P4; u32 m5 = P5; u32 m6 = P6; u32 m7 = P7; u32 m8 = P8; u32 m9 = P9; u32 m10 = P10; u32 m11 = P11; u32 m13 = 0x80; u32 m14 = 416; u32 m15 = 0; // The invariant prefix: steps 0..11, once per outer item. u32 A = 0x67452301; u32 B = 0xefcdab89; u32 C = 0x98badcfe; u32 D = 0x10325476; MD5_PREFIX(A, B, C, D); for (u32 j in 0..INNER) { u32 m12 = p * INNER + j; u32 a = A; u32 b = B; u32 c = C; u32 d = D; MD5_REST(a, b, c, d); total = total + (a + 0x67452301) + (b + 0xefcdab89) + (c + 0x98badcfe) + (d + 0x10325476); } } } i64 t1 = hc_now_us(); if (rep == 0) first = t1 - t0; if (rep == 0 || t1 - t0 < best) best = t1 - t0; }
Stdio.printf("%u %lld %lld\n", total, best, first);#if !ARCH_wasm32 u64 cand = (u64)OUTER * (u64)INNER; u64 hs = best > 0 ? (cand * 1000000) / (u64)best : 0; hc_estr("md5_mask: "); hc_eu64(hs); hc_estr(" H/s (best run "); hc_eu64((u64)best); hc_estr(" us for "); hc_eu64(cand); hc_estr(" candidates)\n");#endif return 0; }The gravitational force on each of 8192 bodies from all the others:
Lines: 63.
// nbody — the gravitational force on each of 8192 bodies from all the others:// O(n^2) arithmetic over small arrays, one item per body. Prints// "<checksum> <best_us> <first_us>"; the checksum counts the bodies pulled right// (positive x force), a coarse figure fast GPU maths does not move.#import "Stdio.xc"#import "Math.xc"#import "Par.xc"#import "../src/include/bench_time.xc"
#define N 8192
float px[N];float py[N];float mass[N];float fx[N];
i32 main(void){ u32 seed = (u32)7; for (u32 i in 0..N) { seed = seed * (u32)1664525 + (u32)1013904223; px[i] = (float)(seed >> (u32)8) / 16777216.0f; seed = seed * (u32)1664525 + (u32)1013904223; py[i] = (float)(seed >> (u32)8) / 16777216.0f; mass[i] = 1.0f + (float)(i % (u32)7); } // Eight runs: the first carries one-off costs (a GPU builds its kernel // then), so the figure is the best run; the first is printed too. Auto // decides after its fourth (two on each device), and uses the rest. u32 right = (u32)0; i64 best = (i64)0; i64 first = (i64)0; for (u32 rep in 0..8) { right = (u32)0; i64 t0 = bench_now_us(); par forces :reduce(+ right) { for (u32 i in 0..N) { float ax = 0.0f; for (u32 j in 0..N) { float dx = px[j] - px[i]; float dy = py[j] - py[i]; float d2 = dx * dx + dy * dy + 0.0001f; ax = ax + mass[j] * dx / (d2 * Math.sqrt(d2)); } fx[i] = ax; if (ax > 0.0f) right = right + (u32)1; } } i64 t1 = bench_now_us(); if (rep == (u32)0) first = t1 - t0; if (rep == (u32)0 || t1 - t0 < best) best = t1 - t0; } Stdio.printf("%u %lld %lld\n", (right + (u32)50) / (u32)100, best, first); return 0;}perlin
Section titled “perlin”Ken Perlin’s improved noise, 2D, four octaves, over a 2048x2048
Lines: 102.
// perlin — Ken Perlin's improved noise, 2D, four octaves, over a 2048x2048// image: a permutation table read at data-dependent places, helper calls,// and one store per pixel. Prints "<checksum> <best_us> <first_us>"; the checksum is// the mean grey level to a tenth.#import "Stdio.xc"#import "Par.xc"#import "../src/include/bench_time.xc"
#define SIZE (2048 * 2048)
u32 perm[512];u8 img[SIZE];
float fade(float t) { return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f); }float lerp(float t, float a, float b) { return a + t * (b - a); }float grad(u32 h, float x, float y){ u32 g = h & (u32)7; float u = g < (u32)4 ? x : y; float v = g < (u32)4 ? y : x; float a = (g & (u32)1) != (u32)0 ? 0.0f - u : u; float b = (g & (u32)2) != (u32)0 ? 0.0f - 2.0f * v : 2.0f * v; return a + b;}
i32 main(void){ u32 seed = (u32)12345; for (u32 i in 0..256) perm[i] = i; for (u32 i in 0..256) { seed = seed * (u32)1103515245 + (u32)12345; u32 j = i + (seed >> (u32)16) % ((u32)256 - i); u32 t = perm[i]; perm[i] = perm[j]; perm[j] = t; } for (u32 i in 0..256) perm[i + (u32)256] = perm[i];
// Eight runs: the first carries one-off costs (a GPU builds its kernel // then), so the figure is the best run; the first is printed too. Auto // decides after its fourth (two on each device), and uses the rest. u32 total = (u32)0; i64 best = (i64)0; i64 first = (i64)0; for (u32 rep in 0..8) { total = (u32)0; i64 t0 = bench_now_us(); par perlin :reduce(+ total) { for (u32 i in 0..SIZE) { float x = (float)(i % (u32)2048) / 256.0f; float y = (float)(i / (u32)2048) / 256.0f; float sum = 0.0f; float amp = 1.0f; float norm = 0.0f; for (u32 o in 0..4) { u32 xi = (u32)x; u32 yi = (u32)y; float xf = x - (float)xi; float yf = y - (float)yi; u32 X = xi & (u32)255; u32 Y = yi & (u32)255; u32 aa = perm[perm[X] + Y]; u32 ab = perm[perm[X] + Y + (u32)1]; u32 ba = perm[perm[X + (u32)1] + Y]; u32 bb = perm[perm[X + (u32)1] + Y + (u32)1]; float u = fade(xf); float v = fade(yf); float n = lerp(v, lerp(u, grad(aa, xf, yf), grad(ba, xf - 1.0f, yf)), lerp(u, grad(ab, xf, yf - 1.0f), grad(bb, xf - 1.0f, yf - 1.0f))); sum = sum + n * amp; norm = norm + amp; amp = amp * 0.5f; x = x * 2.0f; y = y * 2.0f; } float s = (sum / norm) * 0.5f + 0.5f; if (s < 0.0f) s = 0.0f; if (s > 1.0f) s = 1.0f; u32 p = (u32)(s * 255.0f); img[i] = (u8)p; total = total + p; } } i64 t1 = bench_now_us(); if (rep == (u32)0) first = t1 - t0; if (rep == (u32)0 || t1 - t0 < best) best = t1 - t0; } u32 tenths = (total * (u32)10 + (u32)(SIZE / 2)) / (u32)SIZE; Stdio.printf("%u %lld %lld\n", tenths, best, first); return 0;}Y = a*x + y over 16M integers: one multiply-add per element and
Lines: 48.
// saxpy — y = a*x + y over 16M integers: one multiply-add per element and// two arrays to move, so memory, not arithmetic, sets the pace. On a GPU the// copies in and out cost more than the work: this is the block auto keeps on// the CPU. Prints "<checksum> <best_us> <first_us>"; the checksum is exact.#import "Stdio.xc"#import "Par.xc"#import "../src/include/bench_time.xc"
#define SIZE (16 * 1024 * 1024)
u32 xs[SIZE];u32 ys[SIZE];
i32 main(void){ for (u32 i in 0..SIZE) { xs[i] = i * (u32)2654435761; ys[i] = i; } // Eight runs: the first carries one-off costs (a GPU builds its kernel // then), so the figure is the best run; the first is printed too. Auto // decides after its fourth (two on each device), and uses the rest. u32 total = (u32)0; i64 best = (i64)0; i64 first = (i64)0; for (u32 rep in 0..8) { total = (u32)0; i64 t0 = bench_now_us(); par saxpy :reduce(+ total) { for (u32 i in 0..SIZE) { u32 v = (u32)3 * xs[i] + ys[i]; ys[i] = v; total = total + v; } } i64 t1 = bench_now_us(); if (rep == (u32)0) first = t1 - t0; if (rep == (u32)0 || t1 - t0 < best) best = t1 - t0; } Stdio.printf("%u %lld %lld\n", total, best, first); return 0;}