diff --git a/csharp/Platform.Collections.Benchmarks/BitsSetIn16BitsBenchmark.cs b/csharp/Platform.Collections.Benchmarks/BitsSetIn16BitsBenchmark.cs new file mode 100644 index 00000000..170bbe0b --- /dev/null +++ b/csharp/Platform.Collections.Benchmarks/BitsSetIn16BitsBenchmark.cs @@ -0,0 +1,186 @@ +using BenchmarkDotNet.Attributes; +using System; +using System.Runtime.CompilerServices; + +namespace Platform.Collections.Benchmarks +{ + [SimpleJob] + [MemoryDiagnoser] + public class BitsSetIn16BitsBenchmark + { + private static readonly byte[][] _bitsSetIn16BitsLookup; + + // Test data with various bit patterns + private readonly ushort[] _testData = new ushort[] + { + 0x0000, // No bits set + 0x0001, // Single bit + 0x8000, // Single high bit + 0x5555, // Alternating bits + 0xAAAA, // Alternating bits + 0x00FF, // Lower byte set + 0xFF00, // Upper byte set + 0xFFFF, // All bits set + 0x1248, // Sparse bits + 0x7FFE, // Many bits set + }; + + static BitsSetIn16BitsBenchmark() + { + _bitsSetIn16BitsLookup = new byte[65536][]; + int i, c, k; + byte bitIndex; + for (i = 0; i < 65536; i++) + { + // Calculating size of array (number of positive bits) + for (c = 0, k = 1; k <= 65536; k <<= 1) + { + if ((i & k) == k) + { + c++; + } + } + var array = new byte[c]; + // Adding positive bits indices into array + for (bitIndex = 0, c = 0, k = 1; k <= 65536; k <<= 1) + { + if ((i & k) == k) + { + array[c++] = bitIndex; + } + bitIndex++; + } + _bitsSetIn16BitsLookup[i] = array; + } + } + + [Params(1000, 10000, 100000, 1000000)] + public int IterationCount { get; set; } + + [Benchmark(Baseline = true)] + public void UsingLookupTable() + { + for (int iteration = 0; iteration < IterationCount; iteration++) + { + foreach (var value in _testData) + { + var bits = _bitsSetIn16BitsLookup[value]; + // Use bits to prevent optimization + _ = bits.Length; + } + } + } + + [Benchmark] + public void UsingOnDemandCalculation() + { + for (int iteration = 0; iteration < IterationCount; iteration++) + { + foreach (var value in _testData) + { + var bits = CalculateBitsOnDemand(value); + // Use bits to prevent optimization + _ = bits.Length; + } + } + } + + [Benchmark] + public void UsingOnDemandCalculationOptimized() + { + for (int iteration = 0; iteration < IterationCount; iteration++) + { + foreach (var value in _testData) + { + var bits = CalculateBitsOnDemandOptimized(value); + // Use bits to prevent optimization + _ = bits.Length; + } + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte[] CalculateBitsOnDemand(ushort value) + { + // Count set bits first + int count = System.Numerics.BitOperations.PopCount(value); + if (count == 0) + return Array.Empty(); + + var result = new byte[count]; + int index = 0; + for (byte bitPos = 0; bitPos < 16; bitPos++) + { + if ((value & (1 << bitPos)) != 0) + { + result[index++] = bitPos; + } + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte[] CalculateBitsOnDemandOptimized(ushort value) + { + if (value == 0) + return Array.Empty(); + + // Count set bits using built-in PopCount + int count = System.Numerics.BitOperations.PopCount(value); + var result = new byte[count]; + int index = 0; + + // Use bit scanning to find set bits more efficiently + while (value != 0) + { + int bitPos = System.Numerics.BitOperations.TrailingZeroCount(value); + result[index++] = (byte)bitPos; + value &= (ushort)(value - 1); // Clear the lowest set bit + } + return result; + } + + // Benchmark specifically for GetBits method pattern used in BitString + [Benchmark] + public void GetBitsWithLookupTable() + { + for (int iteration = 0; iteration < IterationCount; iteration++) + { + long word = 0x123456789ABCDEF0L; // Test pattern + GetBitsLookup(word, out var bits00to15, out var bits16to31, out var bits32to47, out var bits48to63); + // Use results to prevent optimization + _ = bits00to15.Length + bits16to31.Length + bits32to47.Length + bits48to63.Length; + } + } + + [Benchmark] + public void GetBitsWithOnDemandCalculation() + { + for (int iteration = 0; iteration < IterationCount; iteration++) + { + long word = 0x123456789ABCDEF0L; // Test pattern + GetBitsOnDemand(word, out var bits00to15, out var bits16to31, out var bits32to47, out var bits48to63); + // Use results to prevent optimization + _ = bits00to15.Length + bits16to31.Length + bits32to47.Length + bits48to63.Length; + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetBitsLookup(long word, out byte[] bits00to15, out byte[] bits16to31, out byte[] bits32to47, out byte[] bits48to63) + { + bits00to15 = _bitsSetIn16BitsLookup[word & 0xffffu]; + bits16to31 = _bitsSetIn16BitsLookup[(word >> 16) & 0xffffu]; + bits32to47 = _bitsSetIn16BitsLookup[(word >> 32) & 0xffffu]; + bits48to63 = _bitsSetIn16BitsLookup[(word >> 48) & 0xffffu]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetBitsOnDemand(long word, out byte[] bits00to15, out byte[] bits16to31, out byte[] bits32to47, out byte[] bits48to63) + { + bits00to15 = CalculateBitsOnDemandOptimized((ushort)(word & 0xffffu)); + bits16to31 = CalculateBitsOnDemandOptimized((ushort)((word >> 16) & 0xffffu)); + bits32to47 = CalculateBitsOnDemandOptimized((ushort)((word >> 32) & 0xffffu)); + bits48to63 = CalculateBitsOnDemandOptimized((ushort)((word >> 48) & 0xffffu)); + } + } +} \ No newline at end of file diff --git a/csharp/Platform.Collections.Benchmarks/Program.cs b/csharp/Platform.Collections.Benchmarks/Program.cs index cbde7630..c7649a85 100644 --- a/csharp/Platform.Collections.Benchmarks/Program.cs +++ b/csharp/Platform.Collections.Benchmarks/Program.cs @@ -4,6 +4,6 @@ namespace Platform.Collections.Benchmarks { static class Program { - static void Main() => BenchmarkRunner.Run(); + static void Main() => BenchmarkRunner.Run(); } } diff --git a/experiments/BenchmarkResults.md b/experiments/BenchmarkResults.md new file mode 100644 index 00000000..5127b1fc --- /dev/null +++ b/experiments/BenchmarkResults.md @@ -0,0 +1,82 @@ +# BitsSetIn16Bits Performance Comparison Results + +## Issue Analysis +The issue asks to compare the performance of storing a precomputed lookup table `_bitsSetIn16Bits` versus calculating bit positions on demand. + +## Current Implementation +- **Static field**: `private static readonly byte[][] _bitsSetIn16Bits;` +- **Size**: 65,536 entries (one for each possible 16-bit value) +- **Memory usage**: ~1024KB +- **Initialization time**: ~97ms during static constructor + +## Experiment Results + +### Simple Benchmark (1,000,000 iterations × 10 test patterns) +- **Lookup table approach**: 197ms +- **On-demand calculation**: 4,790ms +- **Result**: Lookup table is **24.31× faster** + +### GetBits Pattern Benchmark (100,000 iterations) +Simulating the actual `GetBits(long word, ...)` method usage: +- **Lookup table approach**: 3ms +- **On-demand calculation**: 125ms +- **Result**: Lookup table is **41.67× faster** + +## Memory vs Performance Trade-off + +### Lookup Table Approach (Current) +**Advantages:** +- Extremely fast O(1) lookup +- 24-42× faster than on-demand calculation +- Predictable performance +- No CPU computation during lookup + +**Disadvantages:** +- Uses ~1024KB of memory +- 97ms initialization time during application startup +- Memory stays allocated for entire application lifetime + +### On-Demand Calculation Approach +**Advantages:** +- Zero memory overhead +- No initialization time +- Uses modern CPU bit manipulation instructions (BitOperations.PopCount, TrailingZeroCount) + +**Disadvantages:** +- 24-42× slower than lookup table +- CPU computation required for each operation +- Variable performance depending on bit patterns + +## Technical Analysis + +The BitString class is clearly performance-critical code with multiple vectorized and parallelized operations. The `GetBits` method is used extensively in: +- `CountSetBitsForWord()` +- `AppendAllSetBitIndices()` +- `GetFirstSetBitForWord()` +- `GetLastSetBitForWord()` + +These methods are called frequently during BitString operations like: +- Counting set bits +- Finding bit indices +- Converting to lists of indices + +## Recommendation + +**Keep the current lookup table approach** for the following reasons: + +1. **Performance is critical**: The 24-42× speedup significantly outweighs the 1MB memory cost +2. **Memory is reasonable**: 1MB is minimal for modern systems +3. **Initialization cost is one-time**: 97ms happens only during static initialization +4. **Usage pattern**: BitString operations are likely to be called many times, making the lookup table very cost-effective +5. **Architecture consistency**: The codebase already shows performance-first design with vectorization and parallelization + +The 1MB memory cost is easily justified by the massive performance improvement, especially in a library designed for high-performance bit operations. + +## Alternative Considerations + +If memory usage becomes a concern in specific scenarios, consider: +1. **Lazy initialization**: Only initialize the lookup table when first used +2. **Configurable behavior**: Allow users to choose between approaches +3. **Hybrid approach**: Use lookup table for frequently accessed patterns, on-demand for others + +However, for the general case, the current lookup table approach is optimal. \ No newline at end of file diff --git a/experiments/BitSetComparison.cs b/experiments/BitSetComparison.cs new file mode 100644 index 00000000..d87bc1e4 --- /dev/null +++ b/experiments/BitSetComparison.cs @@ -0,0 +1,220 @@ +using System; +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace Experiments +{ + class BitSetComparison + { + private static readonly byte[][] _bitsSetIn16BitsLookup; + + static BitSetComparison() + { + Console.WriteLine("Initializing lookup table..."); + var sw = Stopwatch.StartNew(); + + _bitsSetIn16BitsLookup = new byte[65536][]; + int i, c, k; + byte bitIndex; + for (i = 0; i < 65536; i++) + { + // Calculating size of array (number of positive bits) + for (c = 0, k = 1; k <= 65536; k <<= 1) + { + if ((i & k) == k) + { + c++; + } + } + var array = new byte[c]; + // Adding positive bits indices into array + for (bitIndex = 0, c = 0, k = 1; k <= 65536; k <<= 1) + { + if ((i & k) == k) + { + array[c++] = bitIndex; + } + bitIndex++; + } + _bitsSetIn16BitsLookup[i] = array; + } + + sw.Stop(); + Console.WriteLine($"Lookup table initialization took: {sw.ElapsedMilliseconds}ms"); + + // Calculate memory usage + long totalMemory = 0; + for (int j = 0; j < 65536; j++) + { + totalMemory += _bitsSetIn16BitsLookup[j].Length; + } + totalMemory += 65536 * IntPtr.Size; // Array references + Console.WriteLine($"Lookup table memory usage: ~{totalMemory / 1024}KB"); + } + + static void Main(string[] args) + { + Console.WriteLine("Comparing lookup table vs on-demand calculation for BitsSetIn16Bits"); + Console.WriteLine("================================================================"); + + // Test different patterns + ushort[] testPatterns = { + 0x0000, // No bits + 0x0001, // Single bit + 0x8000, // Single high bit + 0x5555, // Alternating + 0xAAAA, // Alternating inverse + 0x00FF, // Lower byte + 0xFF00, // Upper byte + 0xFFFF, // All bits + 0x1248, // Sparse + 0x7FFE // Dense + }; + + const int iterations = 1_000_000; + + // Warm up + for (int i = 0; i < 10000; i++) + { + _ = _bitsSetIn16BitsLookup[0x5555]; + _ = CalculateBitsOnDemandOptimized(0x5555); + } + + // Benchmark lookup table approach + Console.WriteLine("\\nBenchmarking lookup table approach..."); + var sw = Stopwatch.StartNew(); + for (int iter = 0; iter < iterations; iter++) + { + foreach (var pattern in testPatterns) + { + var bits = _bitsSetIn16BitsLookup[pattern]; + _ = bits.Length; // Use to prevent optimization + } + } + sw.Stop(); + var lookupTime = sw.ElapsedMilliseconds; + Console.WriteLine($"Lookup table: {lookupTime}ms for {iterations * testPatterns.Length} operations"); + + // Benchmark on-demand calculation + Console.WriteLine("\\nBenchmarking on-demand calculation..."); + sw.Restart(); + for (int iter = 0; iter < iterations; iter++) + { + foreach (var pattern in testPatterns) + { + var bits = CalculateBitsOnDemandOptimized(pattern); + _ = bits.Length; // Use to prevent optimization + } + } + sw.Stop(); + var onDemandTime = sw.ElapsedMilliseconds; + Console.WriteLine($"On-demand calc: {onDemandTime}ms for {iterations * testPatterns.Length} operations"); + + // Results + Console.WriteLine("\\n=== RESULTS ==="); + Console.WriteLine($"Lookup table: {lookupTime}ms"); + Console.WriteLine($"On-demand calc: {onDemandTime}ms"); + + if (lookupTime < onDemandTime) + { + var speedup = (double)onDemandTime / lookupTime; + Console.WriteLine($"Lookup table is {speedup:F2}x faster"); + } + else + { + var speedup = (double)lookupTime / onDemandTime; + Console.WriteLine($"On-demand calculation is {speedup:F2}x faster"); + } + + // Test with realistic BitString GetBits pattern + Console.WriteLine("\\n=== Testing GetBits Pattern ==="); + TestGetBitsPattern(); + } + + static void TestGetBitsPattern() + { + const int iterations = 100_000; + long testWord = 0x123456789ABCDEF0L; + + // Warm up + for (int i = 0; i < 1000; i++) + { + GetBitsLookup(testWord, out _, out _, out _, out _); + GetBitsOnDemand(testWord, out _, out _, out _, out _); + } + + // Benchmark lookup approach + var sw = Stopwatch.StartNew(); + for (int i = 0; i < iterations; i++) + { + GetBitsLookup(testWord, out var bits00to15, out var bits16to31, out var bits32to47, out var bits48to63); + _ = bits00to15.Length + bits16to31.Length + bits32to47.Length + bits48to63.Length; + } + sw.Stop(); + var lookupTime = sw.ElapsedMilliseconds; + + // Benchmark on-demand approach + sw.Restart(); + for (int i = 0; i < iterations; i++) + { + GetBitsOnDemand(testWord, out var bits00to15, out var bits16to31, out var bits32to47, out var bits48to63); + _ = bits00to15.Length + bits16to31.Length + bits32to47.Length + bits48to63.Length; + } + sw.Stop(); + var onDemandTime = sw.ElapsedMilliseconds; + + Console.WriteLine($"GetBits with lookup: {lookupTime}ms"); + Console.WriteLine($"GetBits with on-demand: {onDemandTime}ms"); + + if (lookupTime < onDemandTime) + { + var speedup = (double)onDemandTime / lookupTime; + Console.WriteLine($"GetBits with lookup is {speedup:F2}x faster"); + } + else + { + var speedup = (double)lookupTime / onDemandTime; + Console.WriteLine($"GetBits with on-demand is {speedup:F2}x faster"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte[] CalculateBitsOnDemandOptimized(ushort value) + { + if (value == 0) + return Array.Empty(); + + // Count set bits using built-in PopCount + int count = System.Numerics.BitOperations.PopCount(value); + var result = new byte[count]; + int index = 0; + + // Use bit scanning to find set bits more efficiently + while (value != 0) + { + int bitPos = System.Numerics.BitOperations.TrailingZeroCount(value); + result[index++] = (byte)bitPos; + value &= (ushort)(value - 1); // Clear the lowest set bit + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetBitsLookup(long word, out byte[] bits00to15, out byte[] bits16to31, out byte[] bits32to47, out byte[] bits48to63) + { + bits00to15 = _bitsSetIn16BitsLookup[word & 0xffffu]; + bits16to31 = _bitsSetIn16BitsLookup[(word >> 16) & 0xffffu]; + bits32to47 = _bitsSetIn16BitsLookup[(word >> 32) & 0xffffu]; + bits48to63 = _bitsSetIn16BitsLookup[(word >> 48) & 0xffffu]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetBitsOnDemand(long word, out byte[] bits00to15, out byte[] bits16to31, out byte[] bits32to47, out byte[] bits48to63) + { + bits00to15 = CalculateBitsOnDemandOptimized((ushort)(word & 0xffffu)); + bits16to31 = CalculateBitsOnDemandOptimized((ushort)((word >> 16) & 0xffffu)); + bits32to47 = CalculateBitsOnDemandOptimized((ushort)((word >> 32) & 0xffffu)); + bits48to63 = CalculateBitsOnDemandOptimized((ushort)((word >> 48) & 0xffffu)); + } + } +} \ No newline at end of file diff --git a/experiments/CorrectnessTest.cs b/experiments/CorrectnessTest.cs new file mode 100644 index 00000000..31b7532d --- /dev/null +++ b/experiments/CorrectnessTest.cs @@ -0,0 +1,187 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Experiments +{ + class CorrectnessTest + { + private static readonly byte[][] _bitsSetIn16BitsLookup; + + static CorrectnessTest() + { + _bitsSetIn16BitsLookup = new byte[65536][]; + int i, c, k; + byte bitIndex; + for (i = 0; i < 65536; i++) + { + // Calculating size of array (number of positive bits) + for (c = 0, k = 1; k <= 65536; k <<= 1) + { + if ((i & k) == k) + { + c++; + } + } + var array = new byte[c]; + // Adding positive bits indices into array + for (bitIndex = 0, c = 0, k = 1; k <= 65536; k <<= 1) + { + if ((i & k) == k) + { + array[c++] = bitIndex; + } + bitIndex++; + } + _bitsSetIn16BitsLookup[i] = array; + } + } + + static void Main(string[] args) + { + Console.WriteLine("Testing correctness of lookup table vs on-demand calculation"); + Console.WriteLine("============================================================"); + + bool allTestsPassed = true; + + // Test all possible 16-bit values (this might take a moment...) + Console.WriteLine("Testing all 65,536 possible 16-bit values..."); + + int errorsFound = 0; + for (int i = 0; i < 65536; i++) + { + ushort value = (ushort)i; + var lookupResult = _bitsSetIn16BitsLookup[value]; + var calculatedResult = CalculateBitsOnDemandOptimized(value); + + if (!lookupResult.SequenceEqual(calculatedResult)) + { + if (errorsFound < 10) // Show first 10 errors + { + Console.WriteLine($"MISMATCH for value {value:X4}:"); + Console.WriteLine($" Lookup: [{string.Join(", ", lookupResult)}]"); + Console.WriteLine($" Calculated: [{string.Join(", ", calculatedResult)}]"); + } + errorsFound++; + allTestsPassed = false; + } + + if (i % 10000 == 0) + { + Console.WriteLine($" Tested {i + 1:N0}/65,536 values..."); + } + } + + if (errorsFound > 10) + { + Console.WriteLine($" ... and {errorsFound - 10} more errors"); + } + + Console.WriteLine($"\\nCompleted testing all 65,536 values"); + Console.WriteLine($"Errors found: {errorsFound}"); + + // Test specific interesting patterns + Console.WriteLine("\\nTesting specific bit patterns:"); + TestSpecificPattern(0x0000, "No bits set"); + TestSpecificPattern(0x0001, "Single bit (LSB)"); + TestSpecificPattern(0x8000, "Single bit (MSB)"); + TestSpecificPattern(0x5555, "Alternating bits (0101...)"); + TestSpecificPattern(0xAAAA, "Alternating bits (1010...)"); + TestSpecificPattern(0x00FF, "Lower byte set"); + TestSpecificPattern(0xFF00, "Upper byte set"); + TestSpecificPattern(0xFFFF, "All bits set"); + + // Test GetBits method equivalence + Console.WriteLine("\\nTesting GetBits method equivalence:"); + TestGetBitsEquivalence(0x123456789ABCDEF0L); + TestGetBitsEquivalence(0x0000000000000000L); + TestGetBitsEquivalence(unchecked((long)0xFFFFFFFFFFFFFFFF)); + TestGetBitsEquivalence(0x5555555555555555L); + + Console.WriteLine($"\\n=== FINAL RESULT ==="); + if (allTestsPassed && errorsFound == 0) + { + Console.WriteLine("✅ ALL TESTS PASSED - Both approaches produce identical results"); + } + else + { + Console.WriteLine("❌ TESTS FAILED - Results differ between approaches"); + } + } + + static void TestSpecificPattern(ushort value, string description) + { + var lookupResult = _bitsSetIn16BitsLookup[value]; + var calculatedResult = CalculateBitsOnDemandOptimized(value); + + bool matches = lookupResult.SequenceEqual(calculatedResult); + string status = matches ? "✅" : "❌"; + + Console.WriteLine($" {status} {description} (0x{value:X4}): Lookup=[{string.Join(",", lookupResult)}], Calculated=[{string.Join(",", calculatedResult)}]"); + } + + static void TestGetBitsEquivalence(long testWord) + { + // Test lookup approach + GetBitsLookup(testWord, out var lookup00to15, out var lookup16to31, out var lookup32to47, out var lookup48to63); + + // Test on-demand approach + GetBitsOnDemand(testWord, out var ondemand00to15, out var ondemand16to31, out var ondemand32to47, out var ondemand48to63); + + bool matches = lookup00to15.SequenceEqual(ondemand00to15) && + lookup16to31.SequenceEqual(ondemand16to31) && + lookup32to47.SequenceEqual(ondemand32to47) && + lookup48to63.SequenceEqual(ondemand48to63); + + string status = matches ? "✅" : "❌"; + Console.WriteLine($" {status} GetBits(0x{testWord:X16})"); + + if (!matches) + { + Console.WriteLine($" 00-15: Lookup=[{string.Join(",", lookup00to15)}], OnDemand=[{string.Join(",", ondemand00to15)}]"); + Console.WriteLine($" 16-31: Lookup=[{string.Join(",", lookup16to31)}], OnDemand=[{string.Join(",", ondemand16to31)}]"); + Console.WriteLine($" 32-47: Lookup=[{string.Join(",", lookup32to47)}], OnDemand=[{string.Join(",", ondemand32to47)}]"); + Console.WriteLine($" 48-63: Lookup=[{string.Join(",", lookup48to63)}], OnDemand=[{string.Join(",", ondemand48to63)}]"); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static byte[] CalculateBitsOnDemandOptimized(ushort value) + { + if (value == 0) + return Array.Empty(); + + // Count set bits using built-in PopCount + int count = System.Numerics.BitOperations.PopCount(value); + var result = new byte[count]; + int index = 0; + + // Use bit scanning to find set bits more efficiently + while (value != 0) + { + int bitPos = System.Numerics.BitOperations.TrailingZeroCount(value); + result[index++] = (byte)bitPos; + value &= (ushort)(value - 1); // Clear the lowest set bit + } + return result; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetBitsLookup(long word, out byte[] bits00to15, out byte[] bits16to31, out byte[] bits32to47, out byte[] bits48to63) + { + bits00to15 = _bitsSetIn16BitsLookup[(int)(word & 0xffffu)]; + bits16to31 = _bitsSetIn16BitsLookup[(int)((word >> 16) & 0xffffu)]; + bits32to47 = _bitsSetIn16BitsLookup[(int)((word >> 32) & 0xffffu)]; + bits48to63 = _bitsSetIn16BitsLookup[(int)((word >> 48) & 0xffffu)]; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void GetBitsOnDemand(long word, out byte[] bits00to15, out byte[] bits16to31, out byte[] bits32to47, out byte[] bits48to63) + { + bits00to15 = CalculateBitsOnDemandOptimized((ushort)(word & 0xffffu)); + bits16to31 = CalculateBitsOnDemandOptimized((ushort)((word >> 16) & 0xffffu)); + bits32to47 = CalculateBitsOnDemandOptimized((ushort)((word >> 32) & 0xffffu)); + bits48to63 = CalculateBitsOnDemandOptimized((ushort)((word >> 48) & 0xffffu)); + } + } +} \ No newline at end of file diff --git a/experiments/experiments.csproj b/experiments/experiments.csproj new file mode 100644 index 00000000..0ad97fc7 --- /dev/null +++ b/experiments/experiments.csproj @@ -0,0 +1,11 @@ + + + + Exe + net8 + latest + enable + Experiments.CorrectnessTest + + + \ No newline at end of file