Skip to content

Instantly share code, notes, and snippets.

@metatablecat
Last active May 2, 2024 17:26
Show Gist options
  • Save metatablecat/1f6cd6f4495f95700eb1a686de4ebe5e to your computer and use it in GitHub Desktop.
Save metatablecat/1f6cd6f4495f95700eb1a686de4ebe5e to your computer and use it in GitHub Desktop.
The fastest luau base64 library in the west. This has been benchmarked within Roblox Luau, results may vary
local SEQ = {
[0] = "A", "B", "C", "D", "E", "F", "G", "H",
"I", "J", "K", "L", "M", "N", "O", "P",
"Q", "R", "S", "T", "U", "V", "W", "X",
"Y", "Z", "a", "b", "c", "d", "e", "f",
"g", "h", "i", "j", "k", "l", "m", "n",
"o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "0", "1", "2", "3",
"4", "5", "6", "7", "8", "9", "+", "/",
}
local STRING_FAST = {}
local INDEX = {[61] = 0, [65] = 0}
for key, val in ipairs(SEQ) do
-- memoization
INDEX[string.byte(val)] = key
end
-- string.char has a MASSIVE overhead, its faster to precompute
-- the values for performance
for i = 0, 255 do
local c = string.char(i)
STRING_FAST[i] = c
end
local b64 = {}
function b64.encode(str)
local len = string.len(str)
local output = table.create(math.ceil(len/4)*4)
local index = 1
for i = 1, len, 3 do
local b0, b1, b2 = string.byte(str, i, i + 2)
local b = bit32.lshift(b0, 16) + bit32.lshift(b1 or 0, 8) + (b2 or 0)
output[index] = SEQ[bit32.extract(b, 18, 6)]
output[index + 1] = SEQ[bit32.extract(b, 12, 6)]
output[index + 2] = b1 and SEQ[bit32.extract(b, 6, 6)] or "="
output[index + 3] = b2 and SEQ[bit32.band(b, 63)] or "="
index += 4
end
return table.concat(output)
end
function b64.decode(hash)
-- given a 24 bit word (4 6-bit letters), decode 3 bytes from it
local len = string.len(hash)
local output = table.create(len * 0.75)
local index = 1
for i = 1, len, 4 do
local c0, c1, c2, c3 = string.byte(hash, i, i + 3)
local b =
bit32.lshift(INDEX[c0], 18)
+ bit32.lshift(INDEX[c1], 12)
+ bit32.lshift(INDEX[c2], 6)
+ (INDEX[c3])
output[index] = STRING_FAST[bit32.extract(b, 16, 8)]
output[index + 1] = c2 ~= "=" and STRING_FAST[bit32.extract(b, 8, 8)] or "="
output[index + 2] = c3 ~= "=" and STRING_FAST[bit32.band(b, 0xFF)] or "="
index += 3
end
return table.concat(output)
end
return b64

The following benchmark was compared against the Base64 library available here using Boatbomber's plugin

Data was tested with 100,000 char strings

decode encode AGF decode AGF encode
10th % 2.751ms 4.755ms 5.185ms 7.554ms
50th % 2.997ms 5.123ms 7.474ms 9.015ms
90th % 3.482ms 5.864ms 8.847ms 10.030ms
Avg 8.934ms 11.329ms 27.008ms 20.659ms
Min 2.519ms 4.468ms 4.417ms 6.623ms
Max 15.348ms 18.191ms 49.599ms 34.694ms
Average bytes per second 111,931,945.377 88,269,044.046 37,026,066.350 48,405,053.487
@metatablecat
Copy link
Author

going to attach some benchmark results, hold on

@phoriah
Copy link

phoriah commented May 2, 2024

going to attach some benchmark results, hold on

You should benchmark it against https://github.com/Reselim/Base64 too

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