Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vectorized ROT-47 cipher #6

Merged
merged 1 commit into from
Dec 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading