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 {
2222 };
2323}
2424
25fn rotate(a: u32, b: u5) u32 {
26 return ((a << b) |
27 (a >> @intCast(u5, (32 - @intCast(u6, b))))
28 );
29}
30
3125// 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
3329 var x: [16]u32 = undefined;
34 var out: [64]u8 = undefined;
3530
3631 for (x) |_, i|
3732 x[i] = input[i];
33
3834 const rounds = comptime []QuarterRound{
3935 Rp( 0, 4, 8,12),
4036 Rp( 1, 5, 9,13),
......@@ -45,20 +41,21 @@ fn salsa20_wordtobyte(input: [16]u32) [64]u8 {
4541 Rp( 2, 7, 8,13),
4642 Rp( 3, 4, 9,14),
4743 };
48 comptime var j: usize = 20;
49 inline while (j > 0) : (j -=2) {
50 for (rounds) |r| {
51 x[r.a] +%= x[r.b]; x[r.d] = rotate(x[r.d] ^ x[r.a], 16);
52 x[r.c] +%= x[r.d]; x[r.b] = rotate(x[r.b] ^ x[r.c], 12);
53 x[r.a] +%= x[r.b]; x[r.d] = rotate(x[r.d] ^ x[r.a], 8);
54 x[r.c] +%= x[r.d]; x[r.b] = rotate(x[r.b] ^ x[r.c], 7);
44
45 comptime var j: usize = 0;
46 inline while (j < 20) : (j += 2) {
47 // two-round cycles
48 inline for (rounds) |r| {
49 x[r.a] +%= x[r.b]; x[r.d] = std.math.rotl(u32, x[r.d] ^ x[r.a], u32(16));
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));
5553 }
5654 }
57 for (x) |_, i|
58 x[i] +%= input[i];
59 for (x) |_, i|
60 mem.writeInt(out[4 * i .. 4 * i + 4], x[i], builtin.Endian.Little);
61 return out;
55
56 for (x) |_, i| {
57 mem.writeInt(out[4 * i .. 4 * i + 4], x[i] +% input[i], builtin.Endian.Little);
58 }
6259}
6360
6461fn 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
7370 mem.readIntLE(u32, c[8..12]),
7471 mem.readIntLE(u32, c[12..16]),
7572 };
76
73
7774 mem.copy(u32, ctx[0..], constant_le[0..4]);
7875 mem.copy(u32, ctx[4..12], key[0..8]);
7976 mem.copy(u32, ctx[12..16], counter[0..4]);
8077
8178 while (true) {
82 var buf = salsa20_wordtobyte(ctx);
79 var buf: [64]u8 = undefined;
80 salsa20_wordtobyte(buf[0..], ctx);
8381
8482 if (remaining < 64) {
8583 var i: usize = 0;
......@@ -88,8 +86,8 @@ fn chaCha20_internal(out: []u8, in: []const u8, key: [8]u32, counter: [4]u32) vo
8886 return;
8987 }
9088
91 comptime var i: usize = 0;
92 inline while (i < 64) : (i += 1)
89 var i: usize = 0;
90 while (i < 64) : (i += 1)
9391 out[cursor + i] = in[cursor + i] ^ buf[i];
9492
9593 cursor += 64;