authorgravatar for marc@tiehu.isMarc Tiehuis <marc@tiehu.is> 2018-08-27 22:55:53-07:00
committergravatar for shawn@git.icuShawn Landden <shawn@git.icu> 2018-08-27 22:55:53-07:00
log87eb95f816b01c0133de47eb3c94ac470f9d8bf2
tree2400b9a02d7da98cff4c0ef50d0e264ace2ae8d4
parent444edd9aed84ebfd9153817259e2a4e1228b7120

speed up chacha20

The main changes are: Unrolling the inner rounds of salsa20_wordtobyte which doubles the speed. Passing the slice explicitly instead of returning the array saves a copy (can optimize out in future with copy elision) and gives ~10% improvement. Inlining the outer loop gives ~15-20% improvement but it costs an extra 4Kb of code space. I think the tradeoff is worthwhile here. The other inline loops are small and can be done by the compiler if it is worthwhile. The rotate function replacement doesn't alter the performance from the former. The modified throughput test I've used to benchmark is as follows. Interestingly we need to allocate memory instead of using a fixed buffer else Zig optimizes the whole thing out. https://github.com/ziglang/zig/pull/1369#issuecomment-416456628

1 files changed, 22 insertions(+), 24 deletions(-)

std/crypto/chacha20.zig+22-24
...@@ -22,19 +22,15 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {...@@ -22,19 +22,15 @@ fn Rp(a: usize, b: usize, c: usize, d: usize) QuarterRound {
22 };22 };
23}23}
2424
25fn rotate(a: u32, b: u5) u32 {
26 return ((a << b) |
27 (a >> @intCast(u5, (32 - @intCast(u6, b))))
28 );
29}
30
31// The chacha family of ciphers are based on the salsa family.25// The chacha family of ciphers are based on the salsa family.
32fn salsa20_wordtobyte(input: [16]u32) [64]u8 {26fn salsa20_wordtobyte(out: []u8, input: [16]u32) void {
27 assert(out.len >= 64);
28
33 var x: [16]u32 = undefined;29 var x: [16]u32 = undefined;
34 var out: [64]u8 = undefined;
3530
36 for (x) |_, i|31 for (x) |_, i|
37 x[i] = input[i];32 x[i] = input[i];
33
38 const rounds = comptime []QuarterRound{34 const rounds = comptime []QuarterRound{
39 Rp( 0, 4, 8,12),35 Rp( 0, 4, 8,12),
40 Rp( 1, 5, 9,13),36 Rp( 1, 5, 9,13),
...@@ -45,20 +41,21 @@ fn salsa20_wordtobyte(input: [16]u32) [64]u8 {...@@ -45,20 +41,21 @@ fn salsa20_wordtobyte(input: [16]u32) [64]u8 {
45 Rp( 2, 7, 8,13),41 Rp( 2, 7, 8,13),
46 Rp( 3, 4, 9,14),42 Rp( 3, 4, 9,14),
47 };43 };
48 comptime var j: usize = 20;44
49 inline while (j > 0) : (j -=2) {45 comptime var j: usize = 0;
50 for (rounds) |r| {46 inline while (j < 20) : (j += 2) {
51 x[r.a] +%= x[r.b]; x[r.d] = rotate(x[r.d] ^ x[r.a], 16);47 // two-round cycles
52 x[r.c] +%= x[r.d]; x[r.b] = rotate(x[r.b] ^ x[r.c], 12);48 inline for (rounds) |r| {
53 x[r.a] +%= x[r.b]; x[r.d] = rotate(x[r.d] ^ x[r.a], 8);49 x[r.a] +%= x[r.b]; x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(16));
54 x[r.c] +%= x[r.d]; x[r.b] = rotate(x[r.b] ^ x[r.c], 7);50 x[r.c] +%= x[r.d]; x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], u32(12));
51 x[r.a] +%= x[r.b]; x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(8));
52 x[r.c] +%= x[r.d]; x[r.b] = std.math.rotl(u32, x[r.b] ^ x[r.c], u32(7));
55 }53 }
56 }54 }
57 for (x) |_, i|55
58 x[i] +%= input[i];56 for (x) |_, i| {
59 for (x) |_, i|57 mem.writeInt(out[4 * i .. 4 * i + 4], x[i] +% input[i], builtin.Endian.Little);
60 mem.writeInt(out[4 * i .. 4 * i + 4], x[i], builtin.Endian.Little);58 }
61 return out;
62}59}
6360
64fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {61fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) void {
...@@ -73,13 +70,14 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo...@@ -73,13 +70,14 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo
73 mem.readIntLE(u32, c[8..12]),70 mem.readIntLE(u32, c[8..12]),
74 mem.readIntLE(u32, c[12..16]),71 mem.readIntLE(u32, c[12..16]),
75 };72 };
76 73
77 mem.copy(u32, ctx[0..], constant_le[0..4]);74 mem.copy(u32, ctx[0..], constant_le[0..4]);
78 mem.copy(u32, ctx[4..12], key[0..8]);75 mem.copy(u32, ctx[4..12], key[0..8]);
79 mem.copy(u32, ctx[12..16], counter[0..4]);76 mem.copy(u32, ctx[12..16], counter[0..4]);
8077
81 while (true) {78 while (true) {
82 var buf = salsa20_wordtobyte(ctx);79 var buf: [64]u8 = undefined;
80 salsa20_wordtobyte(buf[0..], ctx);
8381
84 if (remaining < 64) {82 if (remaining < 64) {
85 var i: usize = 0;83 var i: usize = 0;
...@@ -88,8 +86,8 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo...@@ -88,8 +86,8 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo
88 return;86 return;
89 }87 }
9088
91 comptime var i: usize = 0;89 var i: usize = 0;
92 inline while (i < 64) : (i += 1)90 while (i < 64) : (i += 1)
93 out[cursor + i] = in[cursor + i] ^ buf[i];91 out[cursor + i] = in[cursor + i] ^ buf[i];
9492
95 cursor += 64;93 cursor += 64;