Skip to content

Instantly share code, notes, and snippets.

@vexx32
Created December 6, 2018 19:55
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vexx32/d2b7fff82f93635ea974bdbc0c26fa89 to your computer and use it in GitHub Desktop.
Save vexx32/d2b7fff82f93635ea974bdbc0c26fa89 to your computer and use it in GitHub Desktop.
using System;
using System.Numerics;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;
namespace Benchmark
{
[CoreJob]
[MemoryDiagnoser]
public class BinaryParse
{
[Params(
"1100",
"01101011",
"1100011101011100",
"01111100110011001000010111001101",
"0010010000101101110010000010000001101100010100010010011100110101"/*,
"10010100001010100110100000010100010000101001110010000000101010011100011100111010011011001000100111011110111100001110010110101001",
"1110011111100101100110001111110111000101100110110001001100011110111111010000010011111101111100101001001110001001110000001001110110010100101010001111100111110010010010100100001101100110100001011111111010100001100011100000101000001101000111110001000110100101",
"01010010001111101000001001110001111110001000010010001100011111011110110100110010100010011000010111001011101100111010000111110111001010101111100101101000101010110100101111011001011000111111010000101101110100011100001101010000101110101000111000100000011000110100001111101011000101101011111101010001111101111110011000011101010110001011000010000000000100110000111100110001000001001011110001100000011111100011010001011011011100101000101111011101101100100000011011000111101111000101010100000101010010100101100110111000",
"0101001000111110100000100111000111111000100001001000110001111101111011010011001010001001100001011100101110110011101000011111011100101010111110010110100010101011010010111101100101100011111101000010110111010001110000110101000010111010100011100010000001100011010000111110101100010110101111110101000111110111111001100001110101011000101100001000000000010011000011110011000100000100101111000110000001111110001101000101101101110010100010111101110110110010000001101100011110111100010101010000010101001010010110011011100001010010001111101000001001110001111110001000010010001100011111011110110100110010100010011000010111001011101100111010000111110111001010101111100101101000101010110100101111011001011000111111010000101101110100011100001101010000101110101000111000100000011000110100001111101011000101101011111101010001111101111110011000011101010110001011000010000000000100110000111100110001000001001011110001100000011111100011010001011011011100101000101111011101101100100000011011000111101111000101010100000101010010100101100110111000"
*/)]
public string data;
[GlobalSetup]
public void GlobalSetup()
{
}
public BinaryParse()
{
}
[Benchmark]
public BigInteger CurrentParser() => ParseBinaryCurrent(data);
[Benchmark]
public BigInteger TweakTest() => ParseBinaryTweaked(data);
internal static BigInteger ParseBinaryCurrent(ReadOnlySpan<char> digits, bool unsigned = false)
{
if (!unsigned)
{
if (digits[0] == '0')
{
unsigned = true;
}
else
{
switch (digits.Length)
{
// Only accept sign bits at these lengths:
case 8: // byte
case 16: // short
case 32: // int
case 64: // long
case 96: // decimal
case int n when n >= 128: // BigInteger
break;
default:
// If we do not flag these as unsigned, bigint assumes a sign bit for any (8 * n) string length
unsigned = true;
break;
}
}
}
// Only use heap allocation for very large numbers
const int MaxStackAllocation = 512;
// Calculate number of 8-bit bytes needed to hold the input, rounded up to next whole number.
int outputByteCount = (digits.Length + 7) / 8;
Span<byte> outputBytes = outputByteCount <= MaxStackAllocation ? stackalloc byte[outputByteCount] : new byte[outputByteCount];
int outputByteIndex = outputBytes.Length - 1;
// We need to be prepared for any partial leading bytes, (e.g., 010|00000011|00101100), or cases
// where we only have less than 8 bits to work with from the beginning.
//
// Walk bytes right to left, stepping one whole byte at a time (if there are any whole bytes).
int byteWalker;
for (byteWalker = digits.Length - 1; byteWalker >= 7; byteWalker -= 8)
{
// Use bit shifts and binary-or to sum the values in each byte. These calculations will
// create values higher than a single byte, but the higher bits will be stripped out when cast
// to byte.
//
// The low bits are added in separately to allow us to strip the higher 'noise' bits before we
// sum the values using binary-or.
//
// Simplified representation of logic: (byte)( (7)|(6)|(5)|(4) ) | ( ( (3)|(2)|(1)|(0) ) & 0b1111 )
//
// N.B.: This code has been tested against a straight for loop iterating through the byte, and in no
// circumstance was it faster or more effective than this unrolled version.
outputBytes[outputByteIndex--] =
(byte)(
((digits[byteWalker - 7] << 7)
| (digits[byteWalker - 6] << 6)
| (digits[byteWalker - 5] << 5)
| (digits[byteWalker - 4] << 4)
)
| (
((digits[byteWalker - 3] << 3)
| (digits[byteWalker - 2] << 2)
| (digits[byteWalker - 1] << 1)
| (digits[byteWalker])
) & 0b1111
)
);
}
// With complete bytes parsed, byteWalker is either at the partial byte start index, or at -1
if (byteWalker >= 0)
{
int currentByteValue = 0;
for (int i = 0; i <= byteWalker; i++)
{
currentByteValue = (currentByteValue << 1) | (digits[i] - '0');
}
outputBytes[outputByteIndex] = (byte)currentByteValue;
}
return new BigInteger(outputBytes, isUnsigned: unsigned, isBigEndian: true);
}
internal static BigInteger ParseBinaryTweaked(ReadOnlySpan<char> digits, bool unsigned = false)
{
if (!unsigned)
{
if (digits[0] == '0')
{
unsigned = true;
}
else
{
switch (digits.Length)
{
// Only accept sign bits at these lengths:
case 8: // byte
case 16: // short
case 32: // int
case 64: // long
case 96: // decimal
case int n when n >= 128: // BigInteger
break;
default:
// If we do not flag these as unsigned, bigint assumes a sign bit for any (8 * n) string length
unsigned = true;
break;
}
}
}
// Only use heap allocation for very large numbers; preallocate for compiler optimizations
const int MaxStackAllocation = 512;
Span<byte> outputBytes = stackalloc byte[MaxStackAllocation];
// If we haven't allocated enough, we need to use the heap.
int outputByteCount = (digits.Length + 7) / 8;
if (outputByteCount > MaxStackAllocation)
{
outputBytes = new byte[outputByteCount];
}
int outputByteIndex = outputBytes.Length - 1;
// We need to be prepared for any partial leading bytes, (e.g., 010|00000011|00101100), or cases
// where we only have less than 8 bits to work with from the beginning.
//
// Walk bytes right to left, stepping one whole byte at a time (if there are any whole bytes).
int byteWalker;
for (byteWalker = digits.Length - 1; byteWalker >= 7; byteWalker -= 8)
{
// Use bit shifts and binary-or to sum the values in each byte. These calculations will
// create values higher than a single byte, but the higher bits will be stripped out when cast
// to byte.
//
// The low bits are added in separately to allow us to strip the higher 'noise' bits before we
// sum the values using binary-or.
//
// Simplified representation of logic: (byte)( (7)|(6)|(5)|(4) ) | ( ( (3)|(2)|(1)|(0) ) & 0b1111 )
//
// N.B.: This code has been tested against a straight for loop iterating through the byte, and in no
// circumstance was it faster or more effective than this unrolled version.
outputBytes[outputByteIndex--] =
(byte)(
((digits[byteWalker - 7] << 7)
| (digits[byteWalker - 6] << 6)
| (digits[byteWalker - 5] << 5)
| (digits[byteWalker - 4] << 4)
)
| (
((digits[byteWalker - 3] << 3)
| (digits[byteWalker - 2] << 2)
| (digits[byteWalker - 1] << 1)
| (digits[byteWalker])
) & 0b1111
)
);
}
// With complete bytes parsed, byteWalker is either at the partial byte start index, or at -1
if (byteWalker >= 0)
{
int currentByteValue = 0;
for (int i = 0; i <= byteWalker; i++)
{
currentByteValue = (currentByteValue << 1) | (digits[i] - '0');
}
outputBytes[outputByteIndex] = (byte)currentByteValue;
}
return new BigInteger(outputBytes, isUnsigned: unsigned, isBigEndian: true);
}
}
public class Program
{
public static void Main(string[] args)
{
var summary = BenchmarkRunner.Run<BinaryParse>();
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment