Skip to content

Commit

Permalink
vectorized ROT-47 cipher
Browse files Browse the repository at this point in the history
  • Loading branch information
Peter-Juhasz committed Dec 27, 2024
1 parent 26da0d2 commit a8d81ac
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 13 deletions.
12 changes: 12 additions & 0 deletions docs/performance-improvements.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,18 @@ Payload length: 44 characters

Measured speed up: **14x**

## ROT-47 cipher
In version 2, a fast path was added for shifting ASCII characters:

| | Method | Mean | Error | StdDev | Allocated |
|------|-------------------------- |----------:|---------:|---------:|----------:|
|v1 | Rot47Cipher_General | 23.393 ns | 0.0773 ns | 0.0723 ns | - |
|**v2**| Rot47Cipher_Ascii_Avx2128 | 7.873 ns | 0.0778 ns | 0.0728 ns | - |

Payload length: 44 characters

Measured speed up: **3x**

## Frequency analysis
The old v1 implementation was based on a very simple, but expensive functional LINQ implementation. In the new version, memory allocation was greatly reduced:

Expand Down
2 changes: 1 addition & 1 deletion perf/Science.Cryptography.Ciphers.Benchmarks/Program.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using BenchmarkDotNet.Running;

BenchmarkRunner.Run<AtbashBenchmarks>();
BenchmarkRunner.Run<Rot47Benchmarks>();
//BenchmarkRunner.Run(typeof(Program).Assembly);
29 changes: 29 additions & 0 deletions perf/Science.Cryptography.Ciphers.Benchmarks/Rot47Benchmarks.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
using BenchmarkDotNet.Attributes;

using Science.Cryptography.Ciphers.Specialized;
using System.Collections.Generic;
using System.Linq;
using System;
using Science.Cryptography.Ciphers;

[MemoryDiagnoser]
public class Rot47Benchmarks
{
private static readonly Rot47Cipher General = new();
private static readonly AsciiRot47Cipher Optimized = new();

private const string Plaintext = "The quick brown fox jumps over the lazy dog.";
private static readonly char[] Output = new char[64];

[Benchmark]
public void Atbash()
{
General.Encrypt(Plaintext, Output, out _);
}

[Benchmark]
public void SlowXor_I64_K32()
{
Optimized.Encrypt(Plaintext, Output, out _);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
using System;
using System.Composition;
using System.Numerics;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

using TVector = System.Runtime.Intrinsics.Vector256<short>;

namespace Science.Cryptography.Ciphers.Specialized;

/// <summary>
/// Represents the Atbash cipher.
/// </summary>
[Export("ASCII-ROT-47", typeof(ICipher))]
public class AsciiRot47Cipher : ReciprocalCipher
{
private static readonly TVector VectorOfSpace = Vector256.Create((short)' ');
private static readonly TVector VectorOf47 = Vector256.Create((short)47);
private static readonly TVector VectorOf126 = Vector256.Create((short)126);
private static readonly TVector VectorOf33 = Vector256.Create((short)33);
private static readonly TVector VectorOf94 = Vector256.Create((short)94);

protected override void Crypt(ReadOnlySpan<char> text, Span<char> result, out int written)
{
if (result.Length < text.Length)
{
throw new ArgumentException("Size of output buffer is insufficient.", nameof(result));
}

var totalVectorizedLength = 0;

// process vectorized
if (Avx2.IsSupported && Vector256.IsHardwareAccelerated)
{
var vectorCount = text.Length / TVector.Count;
totalVectorizedLength = vectorCount * TVector.Count;
var inputAsShort = MemoryMarshal.Cast<char, short>(text);
var outputAsShort = MemoryMarshal.Cast<char, short>(result);
for (int offset = 0; offset < totalVectorizedLength; offset += TVector.Count)
{
var inputBlock = Vector256.LoadUnsafe(ref MemoryMarshal.GetReference(inputAsShort[offset..]));
var outputBlock = CryptBlockAvx2(inputBlock);
outputBlock.StoreUnsafe(ref MemoryMarshal.GetReference(outputAsShort[offset..]));
}
}

// process the remaining input
if (totalVectorizedLength < text.Length)
{
var remainingInput = text[totalVectorizedLength..];
var remainingOutput = result[totalVectorizedLength..];
CryptSlow(remainingInput, remainingOutput);
}

written = text.Length;
}

internal static void CryptSlow(ReadOnlySpan<char> text, Span<char> result)
{
for (int i = 0; i < text.Length; i++)
{
var ch = text[i];
if (ch == ' ')
{
result[i] = ' ';
continue;
}

int value = ch + 47;

if (value > 126)
{
value -= 94;
}
else if (value < 33)
{
value += 94;
}

result[i] = (char)value;
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static TVector CryptBlockAvx2(TVector input)
{
// whitespace mask
var spaceMask = Avx2.CompareEqual(VectorOfSpace, input);

// add 47
var transformed = Avx2.Add(input, VectorOf47);

// subtract 94 if greater than 126
var greaterThan126Mask = Avx2.CompareGreaterThan(transformed, VectorOf126);
var subtracted = Avx2.Subtract(transformed, VectorOf94);
transformed = Avx2.BlendVariable(transformed, subtracted, greaterThan126Mask);

// add 94 if less than 33
var lessThan33Mask = Avx2.CompareGreaterThan(VectorOf33, transformed);
var added = Avx2.Add(transformed, VectorOf94);
transformed = Avx2.BlendVariable(transformed, added, lessThan33Mask);

// restore whitespace
transformed = Avx2.BlendVariable(transformed, VectorOfSpace, spaceMask);

return transformed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Science.Cryptography.Ciphers.Specialized;
using System;

namespace Science.Cryptography.Ciphers.Tests;

[TestClass]
public class AsciiRot47CipherTests
{
[TestMethod]
public void Rot47()
{
var cipher = new Rot47Cipher();

const string plaintext = "The quick brown fox jumps over the lazy dog";
const string ciphertext = "%96 BF:4< 3C@H? 7@I ;F>AD @G6C E96 =2KJ 5@8";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}

[TestMethod]
public void AsciiRot47()
{
var cipher = new AsciiRot47Cipher();

const string plaintext = "The quick brown fox jumps over the lazy dog";
const string ciphertext = "%96 BF:4< 3C@H? 7@I ;F>AD @G6C E96 =2KJ 5@8";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}
}
12 changes: 0 additions & 12 deletions tests/Science.Cryptography.Ciphers.Tests/Ciphers/CipherTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,18 +103,6 @@ public void Bacon()
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext), true);
}

[TestMethod]
public void Rot47()
{
var cipher = new Rot47Cipher();

const string plaintext = "My string!";
const string ciphertext = "|J DEC:?8P";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}

[TestMethod]
public void Autokey()
{
Expand Down

0 comments on commit a8d81ac

Please sign in to comment.