Skip to content

Commit

Permalink
Optimize std.endian.big and std.endian.little
Browse files Browse the repository at this point in the history
Instead of a bunch of ByteArray.at/ByteArray.set calls and shifts, the
methods in these modules now use Int.swap_bytes where necessary,
combined with less shifting and fewer ByteArray method calls.

This fixes #732.

Changelog: performance
  • Loading branch information
yorickpeterse committed Jul 31, 2024
1 parent 8b10d31 commit 9423067
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 58 deletions.
56 changes: 28 additions & 28 deletions std/src/std/endian/big.inko
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
# If `value` is greater than the maximum value of a 32-bits unsigned integer,
# the additional bits are ignored (i.e. the value wraps around).
#
# # Panics
#
# This method panics if `into` doesn't contain at least 4 values starting at
# index `at`.
#
# # Examples
#
# ```inko
Expand All @@ -17,15 +22,20 @@
# bytes # => ByteArray.from_array([7, 91, 205, 21])
# ```
fn pub write_i32(value: Int, into: mut ByteArray, at: Int) {
into.set(at, value >> 24)
into.set(at + 1, value >> 16)
into.set(at + 2, value >> 8)
into.set(at + 3, value)
if into.size - at < 4 { panic('the ByteArray must contain at least 4 bytes') }

(into.to_pointer as Int + at as Pointer[UInt32]).0 = value.swap_bytes >>> 32
as UInt32
}

# Writes a value interpreted as a 64-bits signed integer into `into` as a series
# of bytes, starting at the index `at`.
#
# # Panics
#
# This method panics if `into` doesn't contain at least 8 values starting at
# index `at`.
#
# # Examples
#
# ```inko
Expand All @@ -37,22 +47,18 @@ fn pub write_i32(value: Int, into: mut ByteArray, at: Int) {
# bytes # => ByteArray.from_array([7, 91, 205, 21])
# ```
fn pub write_i64(value: Int, into: mut ByteArray, at: Int) {
into.set(at, value >> 56)
into.set(at + 1, value >> 48)
into.set(at + 2, value >> 40)
into.set(at + 3, value >> 32)
into.set(at + 4, value >> 24)
into.set(at + 5, value >> 16)
into.set(at + 6, value >> 8)
into.set(at + 7, value)
if into.size - at < 8 { panic('the ByteArray must contain at least 8 bytes') }

(into.to_pointer as Int + at as Pointer[UInt64]).0 = value.swap_bytes
as UInt64
}

# Reads four bytes starting at `at` as a 32-bits signed integer.
#
# # Panics
#
# This method panics if there are less than four bytes available starting at
# `at`.
# This method panics if `from` doesn't contain at least 4 values starting at
# index `at`.
#
# # Examples
#
Expand All @@ -65,18 +71,17 @@ fn pub write_i64(value: Int, into: mut ByteArray, at: Int) {
# big.read_i32(from: bytes, at: 0) # => 123456789
# ```
fn pub read_i32(from: ref ByteArray, at: Int) -> Int {
(from.get(at) << 24)
| (from.get(at + 1) << 16)
| (from.get(at + 2) << 8)
| from.get(at + 3)
if from.size - at < 4 { panic('the ByteArray must contain at least 4 bytes') }

((from.to_pointer as Int + at as Pointer[UInt32]).0 as Int).swap_bytes >>> 32
}

# Reads eight bytes starting at `at` as a 64-bits signed integer.
#
# # Panics
#
# This method panics if there are less than eight bytes available starting at
# `at`.
# This method panics if `from` doesn't contain at least 8 values starting at
# index `at`.
#
# # Examples
#
Expand All @@ -89,12 +94,7 @@ fn pub read_i32(from: ref ByteArray, at: Int) -> Int {
# big.read_i64(from: bytes, at: 0) # => 123456789
# ```
fn pub read_i64(from: ref ByteArray, at: Int) -> Int {
(from.get(at) << 56)
| (from.get(at + 1) << 48)
| (from.get(at + 2) << 40)
| (from.get(at + 3) << 32)
| (from.get(at + 4) << 24)
| (from.get(at + 5) << 16)
| (from.get(at + 6) << 8)
| from.get(at + 7)
if from.size - at < 8 { panic('the ByteArray must contain at least 8 bytes') }

((from.to_pointer as Int + at as Pointer[UInt64]).0 as Int).swap_bytes
}
54 changes: 26 additions & 28 deletions std/src/std/endian/little.inko
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@
# If `value` is greater than the maximum value of a 32-bits unsigned integer,
# the additional bits are ignored (i.e. the value wraps around).
#
# # Panics
#
# This method panics if `into` doesn't contain at least 4 values starting at
# index `at`.
#
# # Examples
#
# ```inko
Expand All @@ -17,15 +22,19 @@
# bytes # => ByteArray.from_array([21, 205, 91, 7])
# ```
fn pub write_i32(value: Int, into: mut ByteArray, at: Int) {
into.set(at, value)
into.set(at + 1, value >> 8)
into.set(at + 2, value >> 16)
into.set(at + 3, value >> 24)
if into.size - at < 4 { panic('the ByteArray must contain at least 4 bytes') }

(into.to_pointer as Int + at as Pointer[UInt32]).0 = value as UInt32
}

# Writes a value interpreted as a 64-bits signed integer into `into` as a series
# of bytes, starting at the index `at`.
#
# # Panics
#
# This method panics if `into` doesn't contain at least 8 values starting at
# index `at`.
#
# # Examples
#
# ```inko
Expand All @@ -37,22 +46,17 @@ fn pub write_i32(value: Int, into: mut ByteArray, at: Int) {
# bytes # => ByteArray.from_array([21, 205, 91, 7])
# ```
fn pub write_i64(value: Int, into: mut ByteArray, at: Int) {
into.set(at, value)
into.set(at + 1, value >> 8)
into.set(at + 2, value >> 16)
into.set(at + 3, value >> 24)
into.set(at + 4, value >> 32)
into.set(at + 5, value >> 40)
into.set(at + 6, value >> 48)
into.set(at + 7, value >> 56)
if into.size - at < 8 { panic('the ByteArray must contain at least 8 bytes') }

(into.to_pointer as Int + at as Pointer[UInt64]).0 = value as UInt64
}

# Reads four bytes starting at `at` as a 32-bits signed integer.
#
# # Panics
#
# This method panics if there are less than four bytes available starting at
# `at`.
# This method panics if `from` doesn't contain at least 4 values starting at
# index `at`.
#
# # Examples
#
Expand All @@ -65,18 +69,17 @@ fn pub write_i64(value: Int, into: mut ByteArray, at: Int) {
# little.read_i32(from: bytes, at: 0) # => 123456789
# ```
fn pub read_i32(from: ref ByteArray, at: Int) -> Int {
(from.get(at + 3) << 24)
| (from.get(at + 2) << 16)
| (from.get(at + 1) << 8)
| from.get(at)
if from.size - at < 4 { panic('the ByteArray must contain at least 4 bytes') }

(from.to_pointer as Int + at as Pointer[UInt32]).0 as Int
}

# Reads eight bytes starting at `at` as a 64-bits signed integer.
#
# # Panics
#
# This method panics if there are less than eight bytes available starting at
# `at`.
# This method panics if `from` doesn't contain at least 8 values starting at
# index `at`.
#
# # Examples
#
Expand All @@ -89,12 +92,7 @@ fn pub read_i32(from: ref ByteArray, at: Int) -> Int {
# little.read_i64(from: bytes, at: 0) # => 123456789
# ```
fn pub read_i64(from: ref ByteArray, at: Int) -> Int {
(from.get(at + 7) << 56)
| (from.get(at + 6) << 48)
| (from.get(at + 5) << 40)
| (from.get(at + 4) << 32)
| (from.get(at + 3) << 24)
| (from.get(at + 2) << 16)
| (from.get(at + 1) << 8)
| from.get(at)
if from.size - at < 8 { panic('the ByteArray must contain at least 8 bytes') }

(from.to_pointer as Int + at as Pointer[UInt64]).0 as Int
}
6 changes: 5 additions & 1 deletion std/test/std/endian/test_big.inko
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ fn pub tests(t: mut Tests) {
t.equal(big.read_i64(from: b3, at: 0), MIN)
})

t.panic('big.read_i64 with not enough bytes', fn {
t.panic('big.read_i64 with an empty ByteArray', fn {
big.read_i64(from: ByteArray.new, at: 0)
})

t.panic('big.read_i64 with not enough bytes', fn {
big.read_i64(from: ByteArray.filled(with: 0, times: 4), at: 0)
})
}
6 changes: 5 additions & 1 deletion std/test/std/endian/test_little.inko
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,11 @@ fn pub tests(t: mut Tests) {
t.equal(little.read_i64(from: b3, at: 0), MIN)
})

t.panic('little.read_i64 with not enough bytes', fn {
t.panic('little.read_i64 with an empty ByteArray', fn {
little.read_i64(from: ByteArray.new, at: 0)
})

t.panic('little.read_i64 with not enough bytes', fn {
little.read_i64(from: ByteArray.filled(with: 0, times: 4), at: 0)
})
}

0 comments on commit 9423067

Please sign in to comment.