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 Atbash cipher for ASCII #5

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
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ public class AtbashBenchmarks
private static readonly AtbashCipher General = new();
private static readonly AsciiAtbashCipher Optimized = new();

private static readonly char[] Input = new char[43];
private static readonly char[] Output = new char[43];
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(Input, Output, out _);
General.Encrypt(Plaintext, Output, out _);
}

[Benchmark]
public void SlowXor_I64_K32()
{
Optimized.Encrypt(Input, Output, out _);
Optimized.Encrypt(Plaintext, Output, out _);
}
}
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<ShiftCipherBenchmarks>();
BenchmarkRunner.Run<AtbashBenchmarks>();
//BenchmarkRunner.Run(typeof(Program).Assembly);
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
using System;
using System.Composition;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
using System.Runtime.Intrinsics.X86;

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

namespace Science.Cryptography.Ciphers.Specialized;

Expand All @@ -9,8 +15,15 @@ namespace Science.Cryptography.Ciphers.Specialized;
[Export("ASCII-Atbash", typeof(ICipher))]
public class AsciiAtbashCipher : ReciprocalCipher
{
private const int A = (int)'A';
private const int a = (int)'a';
private const int APlusZ = (int)('A' + 'Z');
private const int LowercaseAPlusZ = (int)('a' + 'z');

private static readonly TVector VectorOfAPlusZ = Vector128.Create((short)('A' + 'Z'));
private static readonly TVector VectorOfLowercaseAPlusZ = Vector128.Create((short)('a' + 'z'));
private static readonly TVector VectorOfAMinus1 = Vector128.Create((short)('A' - 1));
private static readonly TVector VectorOfZPlus1 = Vector128.Create((short)('Z' + 1));
private static readonly TVector VectorOfLowercaseAMinus1 = Vector128.Create((short)('a' - 1));
private static readonly TVector VectorOfLowercaseZPlus1 = Vector128.Create((short)('z' + 1));

protected override void Crypt(ReadOnlySpan<char> text, Span<char> result, out int written)
{
Expand All @@ -19,18 +32,89 @@ protected override void Crypt(ReadOnlySpan<char> text, Span<char> result, out in
throw new ArgumentException("Size of output buffer is insufficient.", nameof(result));
}

if (Avx2.IsSupported)
{
// process the vectorized input
var vectorCount = text.Length / TVector.Count;
var totalVectorizedLength = vectorCount * TVector.Count;
var vectorizedText = MemoryMarshal.Cast<char, short>(text);
var vectorizedResult = MemoryMarshal.Cast<char, short>(result);
for (int offset = 0; offset < totalVectorizedLength; offset += TVector.Count)
{
var input = Vector128.LoadUnsafe(ref MemoryMarshal.GetReference(vectorizedText[offset..]));
var output = CryptBlockAvx2(input);
output.StoreUnsafe(ref MemoryMarshal.GetReference(vectorizedResult[offset..]));
}

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

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];
var idx = (int)ch;
result[i] = ch switch
{
>= 'A' and <= 'Z' => (char)(A + (25 - (idx - A))),
>= 'a' and <= 'z' => (char)(a + (25 - (idx - a))),
>= 'A' and <= 'Z' => (char)(APlusZ - idx),
>= 'a' and <= 'z' => (char)(LowercaseAPlusZ - idx),
_ => ch,
};
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static TVector CryptBlockAvx2(TVector input)
{
// uppercase
var isUpperMask = Avx2.And(
Avx2.CompareGreaterThan(input, VectorOfAMinus1),
Avx2.CompareLessThan(input, VectorOfZPlus1)
);
TVector transformedUppercase;
if (isUpperMask == TVector.Zero)
{
transformedUppercase = input;
}
else
{
transformedUppercase = Avx2.Subtract(VectorOfAPlusZ, input);
transformedUppercase = Avx2.BlendVariable(input, transformedUppercase, isUpperMask);
}

// lowercase
var isLowerMask = Avx2.And(
Avx2.CompareGreaterThan(input, VectorOfLowercaseAMinus1),
Avx2.CompareLessThan(input, VectorOfLowercaseZPlus1)
);
TVector transformedLowercase;
if (isLowerMask == TVector.Zero)
{
transformedLowercase = input;
}
else
{
transformedLowercase = Avx2.Subtract(VectorOfLowercaseAPlusZ, input);
transformedLowercase = Avx2.BlendVariable(transformedLowercase, transformedLowercase, isLowerMask);
}

// merge
var transformed = Avx2.BlendVariable(transformedUppercase, transformedLowercase, isLowerMask);

written = text.Length;
return transformed;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Science.Cryptography.Ciphers.Specialized;

namespace Science.Cryptography.Ciphers.Tests;

[TestClass]
public class AsciiAtbashCipherTests
{
[TestMethod]
public void AsciiAtbash()
{
var cipher = new AsciiAtbashCipher();

const string plaintext = "AbcdefghijklmnopqrstuvwxyzZYX";
const string ciphertext = "ZyxwvutsrqponmlkjihgfedcbaABC";

Assert.AreEqual(ciphertext, cipher.Encrypt(plaintext));
Assert.AreEqual(plaintext, cipher.Decrypt(ciphertext));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;

using Science.Cryptography.Ciphers.Specialized;

namespace Science.Cryptography.Ciphers.Tests;

[TestClass]
public class AtbashCipherTests
{
[TestMethod]
public void Atbash()
{
var cipher = new AtbashCipher();

const string plaintext = "Abcdefghijklmnopqrstuvwxyz";
const string ciphertext = "Zyxwvutsrqponmlkjihgfedcba";

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

[TestMethod]
public void Atbash()
{
var cipher = new AtbashCipher();

const string plaintext = "Abcdefghijklmnopqrstuvwxyz";
const string ciphertext = "Zyxwvutsrqponmlkjihgfedcba";

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

[TestMethod]
public void AsciiAtbash()
{
var cipher = new AsciiAtbashCipher();

const string plaintext = "Abcdefghijklmnopqrstuvwxyz";
const string ciphertext = "Zyxwvutsrqponmlkjihgfedcba";

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

/*
[TestMethod]
public void Bifid()
Expand Down
Loading