Skip to content

Instantly share code, notes, and snippets.

@batiati
Created October 14, 2022 12:52
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 batiati/53f937e0dce406b22d827237a8eec211 to your computer and use it in GitHub Desktop.
Save batiati/53f937e0dce406b22d827237a8eec211 to your computer and use it in GitHub Desktop.
Bitcast demo
using System;
using System.Runtime.InteropServices;
public static class BenchDecode
{
const int TRANSFER = 128;
[StructLayout(LayoutKind.Sequential, Size = 16)]
unsafe struct UInt128
{
public fixed byte bytes[16];
}
[StructLayout(LayoutKind.Sequential, Size = TRANSFER)]
unsafe struct Transfer
{
public UInt128 id;
public UInt128 debit_account_id;
public UInt128 credit_account_id;
public UInt128 user_data;
public UInt128 reserved;
public UInt128 pending_id;
public ulong timeout;
public uint ledger;
public ushort code;
public ushort flags;
public ulong amount;
public ulong timestamp;
}
public static void Main() {
var buffer = Load("transfers");
Console.WriteLine("do this a few times to let CLR optimize...");
int loops = 10;
while (loops-- > 0)
{
int offset = 0;
ulong sum = 0UL;
var now = DateTime.Now;
while (offset < buffer.Length)
{
var array = buffer[offset..][0..TRANSFER];
// Deserialize without much overhead:
var transfer = MemoryMarshal.AsRef<Transfer>(array);
sum += transfer.amount;
offset += TRANSFER;
}
TimeSpan elapsed = DateTime.Now - now;
Console.WriteLine(" C#: sum of transfer amounts={0} ms={1:0.000}", sum, elapsed.TotalMilliseconds);
}
}
static Span<byte> Load(string file)
{
return System.IO.File.ReadAllBytes(file).AsSpan(0);
}
}
package main
import (
"fmt"
"os"
"time"
"unsafe"
)
type Uint128 struct {
data [16]byte
}
type Transfer struct {
id Uint128
debit_account_id Uint128
credit_account_id Uint128
user_data Uint128
reserved Uint128
pending_iD Uint128
timeout uint64
ledger uint32
code uint16
flags uint16
amount uint64
timestamp uint64
}
const TRANSFER int = 128
func main() {
buffer := load("transfers")
var loops int = 10
for loops > 0 {
var offset int = 0
var sum uint64 = 0
now := time.Now()
for offset < len(buffer) {
// Deserialize without any overhead:
transfer := (*Transfer)(unsafe.Pointer(&buffer[offset]))
sum += transfer.amount
offset += TRANSFER
}
elapsed := time.Since(now)
fmt.Printf(" go: sum of transfer amounts=%d ns=%d\n", sum, elapsed.Nanoseconds())
loops -= 1
}
}
func load(name string) []byte {
file, openErr := os.Open(name)
if openErr != nil {
panic(openErr)
}
defer file.Close()
stats, statsErr := file.Stat()
if statsErr != nil {
panic(statsErr)
}
var size int64 = stats.Size()
bytes := make([]byte, size)
_, readErr := file.Read(bytes)
if readErr != nil {
panic(readErr)
}
return bytes
}
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.concurrent.TimeUnit;
public class BenchDecode {
static final int TRANSFER = 128;
static final int TRANSFER_ID = 16;
static final int TRANSFER_DEBIT_ID = 16;
static final int TRANSFER_CREDIT_ID = 16;
static final int TRANSFER_USER_DATA = 16;
static final int TRANSFER_RESERVED = 16;
static final int TRANSFER_PENDING_ID = 16;
static final int TRANSFER_TIMEOUT = 8;
static final int TRANSFER_LEDGER = 4;
static final int TRANSFER_CODE = 2;
static final int TRANSFER_FLAGS = 2;
static final int TRANSFER_AMOUNT = 8;
static final int TRANSFER_TIMESTAMP = 8;
static final int TRANSFER_ID_OFFSET = 0;
static final int TRANSFER_DEBIT_ID_OFFSET = 0 + 16;
static final int TRANSFER_CREDIT_ID_OFFSET = 0 + 16 + 16;
static final int TRANSFER_USER_DATA_OFFSET = 0 + 16 + 16 + 16;
static final int TRANSFER_RESERVED_OFFSET = 0 + 16 + 16 + 16 + 16;
static final int TRANSFER_PENDING_ID_OFFSET = 0 + 16 + 16 + 16 + 16 + 16;
static final int TRANSFER_TIMEOUT_OFFSET = 0 + 16 + 16 + 16 + 16 + 16 + 16;
static final int TRANSFER_LEDGER_OFFSET = 0 + 16 + 16 + 16 + 16 + 16 + 16 + 8;
static final int TRANSFER_CODE_OFFSET = 0 + 16 + 16 + 16 + 16 + 16 + 16 + 8 + 4;
static final int TRANSFER_FLAGS_OFFSET = 0 + 16 + 16 + 16 + 16 + 16 + 16 + 8 + 4 + 2;
static final int TRANSFER_AMOUNT_OFFSET = 0 + 16 + 16 + 16 + 16 + 16 + 16 + 8 + 4 + 2 + 2;
static final int TRANSFER_TIMESTAMP_OFFSET = 0 + 16 + 16 + 16 + 16 + 16 + 16 + 8 + 4 + 2 + 2 + 8;
public static void main(String[] args) {
final var buffer = load("transfers");
System.out.println("do this a few times to let JVM optimize...");
int loops = 10;
while (loops-- > 0) {
final var now = System.nanoTime();
long sum = 0;
var offset = 0;
while (offset < buffer.capacity()) {
final var id_lsb = buffer.getLong(offset + TRANSFER_ID_OFFSET);
final var id_msb = buffer.getLong(offset + TRANSFER_ID_OFFSET + 8);
final var credit_id_lsb = buffer.getLong(offset + TRANSFER_CREDIT_ID_OFFSET);
final var credit_id_msb = buffer.getLong(offset + TRANSFER_CREDIT_ID_OFFSET + 8);
final var debit_id_lsb = buffer.getLong(offset + TRANSFER_DEBIT_ID_OFFSET);
final var debit_id_msb = buffer.getLong(offset + TRANSFER_DEBIT_ID_OFFSET + 8);
final var user_data_lsb = buffer.getLong(offset + TRANSFER_USER_DATA_OFFSET);
final var user_data_msb = buffer.getLong(offset + TRANSFER_USER_DATA_OFFSET + 8);
final var reserved_lsb = buffer.getLong(offset + TRANSFER_RESERVED_OFFSET);
final var reserved_msb = buffer.getLong(offset + TRANSFER_RESERVED_OFFSET + 8);
final var pending_lsb = buffer.getLong(offset + TRANSFER_PENDING_ID_OFFSET);
final var pending_msb = buffer.getLong(offset + TRANSFER_PENDING_ID_OFFSET + 8);
final var timeout = buffer.getLong(offset + TRANSFER_TIMEOUT_OFFSET);
final var ledger = buffer.getInt(offset + TRANSFER_LEDGER_OFFSET);
final var code = buffer.getShort(offset + TRANSFER_CODE_OFFSET);
final var flags = buffer.getShort(offset + TRANSFER_FLAGS_OFFSET);
final var amount = buffer.getLong(offset + TRANSFER_AMOUNT_OFFSET);
final var timestamp = buffer.getLong(offset + TRANSFER_TIMESTAMP_OFFSET);
sum += amount;
offset += TRANSFER;
}
final double elapsed_ms = (System.nanoTime() - now) / (double)TimeUnit.MILLISECONDS.toNanos(1);
System.out.printf("java: sum of transfer amounts=%s ms=%.3f%n", sum, elapsed_ms);
}
}
private static ByteBuffer load(String file) {
try (final var stream = new FileInputStream(file)) {
return ByteBuffer.wrap(stream.readAllBytes()).order(ByteOrder.nativeOrder()).position(0);
} catch (IOException exception) {
exception.printStackTrace();
System.exit(-1);
return null;
}
}
}
@batiati
Copy link
Author

batiati commented Oct 14, 2022

Deserializing 16,384 transfers on each language:

Language Avg time(ms)
Zig 0.03
Go 0.03
C# 0.05
Java 0.85
JS 5.00

JS and Zig demos
https://github.com/tigerbeetledb/tigerbeetle/tree/main/demos/bitcast

Closed PR
tigerbeetle/tigerbeetle#195

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment