To complement the answer provided by @theraot, here are some timings.
- Test #0: Uses typed arrays and
store_var() without any further optimization.
- Test #0b: Identical to #0 but joins the string array into a single string.
- Test #1: Uses packed arrays, but without conversion to packed byte array.
- Test #2: Uses packed arrays, joined strings, conversion to packed byte array, and still relies on
store_var().
- Test #3: Same as #2 bur writes arrays separately via
store_buffer().
Timings include all steps required (byte array conversion, string join etc.).
There is two-fold decrease in size between #0 and #1, and many-fold increase in speed between #2 VS #0 and #1. There is no big difference between #2 and #3.
Joining strings seems to have massive impact as well.
TEST #0 WRITE: ELAPSED MSEC: 220 FILE SIZE MB: 30.5176544189453
TEST #0 READ: ELAPSED MSEC: 232
TEST #0b WRITE: ELAPSED MSEC: 93 FILE SIZE MB: 20.0272369384766
TEST #0b READ: ELAPSED MSEC: 139
TEST #1 WRITE: ELAPSED MSEC: 172 FILE SIZE MB: 16.2125396728516
TEST #1 READ: ELAPSED MSEC: 101
TEST #2 WRITE: ELAPSED MSEC: 20 FILE SIZE MB: 12.3978424072266
TEST #2 READ: ELAPSED MSEC: 83
TEST #3 WRITE: ELAPSED MSEC: 18 FILE SIZES MB: [3.814697265625, 0.95367431640625, 7.62939453125]
TEST #3 READ: ELAPSED MSEC: 85
Here's a GDScript snippet to execute the above:
extends Control
var array_size := 1_000_000
var strings0: Array[String]
var bools0: Array[bool]
var ints0: Array[int]
var strings: PackedStringArray
var bools: PackedByteArray
var ints: PackedInt64Array
var start: int
var finish: int
var file: FileAccess
var MB := 1024*1024
func _ready():
set_up()
test_0()
print("")
test_0b()
print("")
test_1()
print("")
test_2()
print("")
test_3()
func set_up():
for array in [strings, bools, ints, strings0, bools0, ints0]:
array.resize(array_size)
strings0.fill(".")
strings.fill(".")
ints0.fill(int(Time.get_unix_time_from_system() * 1000))
ints.fill(int(Time.get_unix_time_from_system() * 1000))
for i in array_size:
bools0[i] = i % 2 == 0
bools[i] = int(i % 2 == 0)
func test_0():
start = Time.get_ticks_msec()
file = FileAccess.open("user://test0", FileAccess.WRITE)
file.store_var({
"strings": strings0,
"bools": bools0,
"ints": ints0
})
finish = Time.get_ticks_msec()
prints("TEST #0 WRITE: ELAPSED MSEC:", finish - start, "FILE SIZE MB:", file.get_length() as float / MB)
file.close()
start = Time.get_ticks_msec()
file = FileAccess.open("user://test0", FileAccess.READ)
var data: Dictionary = file.get_var()
finish = Time.get_ticks_msec()
prints("TEST #0 READ: ELAPSED MSEC:", finish - start)
func test_0b():
start = Time.get_ticks_msec()
file = FileAccess.open("user://test0b", FileAccess.WRITE)
file.store_var({
"strings": "".join(strings0),
"bools": bools0,
"ints": ints0
})
finish = Time.get_ticks_msec()
prints("TEST #0b WRITE: ELAPSED MSEC:", finish - start, "FILE SIZE MB:", file.get_length() as float / MB)
file.close()
start = Time.get_ticks_msec()
file = FileAccess.open("user://test0b", FileAccess.READ)
var data: Dictionary = file.get_var()
data.strings = data.strings.split("")
finish = Time.get_ticks_msec()
prints("TEST #0b READ: ELAPSED MSEC:", finish - start)
func test_1():
start = Time.get_ticks_msec()
file = FileAccess.open("user://test1", FileAccess.WRITE)
file.store_var({
"strings": strings,
"bools": bools,
"ints": ints
})
finish = Time.get_ticks_msec()
prints("TEST #1 WRITE: ELAPSED MSEC:", finish - start, "FILE SIZE MB:", file.get_length() as float / MB)
file.close()
start = Time.get_ticks_msec()
file = FileAccess.open("user://test1", FileAccess.READ)
var data: Dictionary = file.get_var()
finish = Time.get_ticks_msec()
prints("TEST #1 READ: ELAPSED MSEC:", finish - start)
func test_2():
start = Time.get_ticks_msec()
file = FileAccess.open("user://test2", FileAccess.WRITE)
file.store_var({
"strings": "".join(strings).to_utf32_buffer(),
"bools": bools,
"ints": ints.to_byte_array()
})
finish = Time.get_ticks_msec()
prints("TEST #2 WRITE: ELAPSED MSEC:", finish - start, "FILE SIZE MB:", file.get_length() as float / MB)
file.close()
start = Time.get_ticks_msec()
file = FileAccess.open("user://test2", FileAccess.READ)
var data: Dictionary = file.get_var()
data.strings = data.strings.get_string_from_utf32().split("")
data.ints = data.ints.to_int64_array()
finish = Time.get_ticks_msec()
prints("TEST #2 READ: ELAPSED MSEC:", finish - start)
func test_3():
var file_sizes: PackedFloat64Array
start = Time.get_ticks_msec()
file = FileAccess.open("user://strings", FileAccess.WRITE)
file.store_buffer("".join(strings).to_utf32_buffer())
file_sizes.append(file.get_length() as float / MB)
file.close()
file = FileAccess.open("user://bools", FileAccess.WRITE)
file.store_buffer(bools)
file_sizes.append(file.get_length() as float / MB)
file.close()
file = FileAccess.open("user://ints", FileAccess.WRITE)
file.store_buffer(ints.to_byte_array())
file_sizes.append(file.get_length() as float / MB)
file.close()
finish = Time.get_ticks_msec()
prints("TEST #3 WRITE: ELAPSED MSEC:", finish - start, "FILE SIZES MB:", file_sizes)
file.close()
start = Time.get_ticks_msec()
file = FileAccess.open("user://strings", FileAccess.READ)
strings = file.get_buffer(file.get_length()).get_string_from_utf32().split("")
file.close()
file = FileAccess.open("user://bools", FileAccess.READ)
bools = file.get_buffer(file.get_length())
file.close()
file = FileAccess.open("user://ints", FileAccess.READ)
ints = file.get_buffer(file.get_length()).to_int64_array()
file.close()
finish = Time.get_ticks_msec()
prints("TEST #3 READ: ELAPSED MSEC:", finish - start)