mirror of
https://github.com/asmjit/asmjit.git
synced 2025-12-18 13:04:36 +03:00
[Bug] Fixed a string buffer growing strategy
For some reason the growing strategy of asmjit::String was too aggressive, basically reaching the maximum doubling capacity too fast (after the first reallocation). This code adapts the current vector growing strategy to be used also by asmjit::String, which doubles the capacity until a threshold is reached and then grows linearly.
This commit is contained in:
16
.github/workflows/build.yml
vendored
16
.github/workflows/build.yml
vendored
@@ -3,6 +3,10 @@ on:
|
|||||||
push:
|
push:
|
||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{github.ref}}
|
||||||
|
cancel-in-progress: ${{github.ref != 'refs/heads/master'}}
|
||||||
|
|
||||||
defaults:
|
defaults:
|
||||||
run:
|
run:
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -133,14 +137,14 @@ jobs:
|
|||||||
- { title: "windows" , host: "windows-2022" , arch: "x64" , cc: "vs2022" , conf: "Debug" , defs: "ASMJIT_TEST=1" }
|
- { title: "windows" , host: "windows-2022" , arch: "x64" , cc: "vs2022" , conf: "Debug" , defs: "ASMJIT_TEST=1" }
|
||||||
- { title: "windows" , host: "windows-2022" , arch: "x64" , cc: "vs2022" , conf: "Release", defs: "ASMJIT_TEST=1" }
|
- { title: "windows" , host: "windows-2022" , arch: "x64" , cc: "vs2022" , conf: "Release", defs: "ASMJIT_TEST=1" }
|
||||||
|
|
||||||
- { title: "freebsd" , host: "macos-12" , arch: "x86-64" , cc: "clang" , conf: "Release", vm: "freebsd", vm_ver: "13.2", defs: "ASMJIT_TEST=1" }
|
- { title: "freebsd" , host: "ubuntu-latest" , arch: "x64" , cc: "clang" , conf: "Release", vm: "freebsd", vm_ver: "14.1", defs: "ASMJIT_TEST=1" }
|
||||||
- { title: "netbsd" , host: "macos-12" , arch: "x86-64" , cc: "clang" , conf: "Release", vm: "netbsd" , vm_ver: "9.3" , defs: "ASMJIT_TEST=1" }
|
- { title: "freebsd" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "freebsd", vm_ver: "14.1", defs: "ASMJIT_TEST=1" }
|
||||||
- { title: "openbsd" , host: "macos-12" , arch: "x86-64" , cc: "clang" , conf: "Release", vm: "openbsd", vm_ver: "7.4" , defs: "ASMJIT_TEST=1" }
|
- { title: "netbsd" , host: "ubuntu-latest" , arch: "x64" , cc: "clang" , conf: "Release", vm: "netbsd" , vm_ver: "10.0", defs: "ASMJIT_TEST=1" }
|
||||||
|
- { title: "netbsd" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "netbsd" , vm_ver: "10.0", defs: "ASMJIT_TEST=1" }
|
||||||
|
- { title: "openbsd" , host: "ubuntu-latest" , arch: "x64" , cc: "clang" , conf: "Release", vm: "openbsd", vm_ver: "7.4" , defs: "ASMJIT_TEST=1" }
|
||||||
- { title: "openbsd" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "openbsd", vm_ver: "7.4" , defs: "ASMJIT_TEST=1" }
|
- { title: "openbsd" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "openbsd", vm_ver: "7.4" , defs: "ASMJIT_TEST=1" }
|
||||||
|
|
||||||
# arm/v7 VM image doesn't work on CI environment at the moment
|
- { title: "debian" , host: "ubuntu-latest" , arch: "arm/v7" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
||||||
# - { title: "debian" , host: "ubuntu-latest" , arch: "arm/v7" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
|
||||||
|
|
||||||
- { title: "debian" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
- { title: "debian" , host: "ubuntu-latest" , arch: "arm64" , cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
||||||
- { title: "debian" , host: "ubuntu-latest" , arch: "riscv64", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
- { title: "debian" , host: "ubuntu-latest" , arch: "riscv64", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
||||||
- { title: "debian" , host: "ubuntu-latest" , arch: "ppc64le", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
- { title: "debian" , host: "ubuntu-latest" , arch: "ppc64le", cc: "clang" , conf: "Release", vm: "debian:unstable", defs: "ASMJIT_TEST=1" }
|
||||||
|
|||||||
@@ -14,9 +14,51 @@ ASMJIT_BEGIN_NAMESPACE
|
|||||||
|
|
||||||
static const char String_baseN[] = "0123456789ABCDEF";
|
static const char String_baseN[] = "0123456789ABCDEF";
|
||||||
|
|
||||||
constexpr size_t kMinAllocSize = 64;
|
constexpr size_t kMinAllocSize = 128;
|
||||||
constexpr size_t kMaxAllocSize = SIZE_MAX - Globals::kGrowThreshold;
|
constexpr size_t kMaxAllocSize = SIZE_MAX - Globals::kGrowThreshold;
|
||||||
|
|
||||||
|
// Based on ZoneVector_growCapacity().
|
||||||
|
//
|
||||||
|
// NOTE: The sizes here include null terminators - that way we can have aligned allocations that are power of 2s
|
||||||
|
// initially.
|
||||||
|
static ASMJIT_FORCE_INLINE size_t String_growCapacity(size_t byteSize, size_t minimumByteSize) noexcept {
|
||||||
|
static constexpr size_t kGrowThreshold = Globals::kGrowThreshold;
|
||||||
|
|
||||||
|
ASMJIT_ASSERT(minimumByteSize < kMaxAllocSize);
|
||||||
|
|
||||||
|
// This is more than exponential growth at the beginning.
|
||||||
|
if (byteSize < kMinAllocSize) {
|
||||||
|
byteSize = kMinAllocSize;
|
||||||
|
}
|
||||||
|
else if (byteSize < 512) {
|
||||||
|
byteSize = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (byteSize < minimumByteSize) {
|
||||||
|
// Exponential growth before we reach `kGrowThreshold`.
|
||||||
|
byteSize = Support::alignUpPowerOf2(minimumByteSize);
|
||||||
|
|
||||||
|
// Bail to `minimumByteSize` in case of overflow - most likely whatever that is happening afterwards would just fail.
|
||||||
|
if (byteSize < minimumByteSize) {
|
||||||
|
return minimumByteSize;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pretty much chunked growth advancing by `kGrowThreshold` after we exceed it.
|
||||||
|
if (byteSize > kGrowThreshold) {
|
||||||
|
// Align to kGrowThreshold.
|
||||||
|
size_t remainder = minimumByteSize % kGrowThreshold;
|
||||||
|
|
||||||
|
byteSize = minimumByteSize + remainder;
|
||||||
|
|
||||||
|
// Bail to `minimumByteSize` in case of overflow.
|
||||||
|
if (byteSize < minimumByteSize)
|
||||||
|
return minimumByteSize;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Support::min<size_t>(byteSize, kMaxAllocSize);
|
||||||
|
}
|
||||||
|
|
||||||
// String - Clear & Reset
|
// String - Clear & Reset
|
||||||
// ======================
|
// ======================
|
||||||
|
|
||||||
@@ -49,13 +91,13 @@ char* String::prepare(ModifyOp op, size_t size) noexcept {
|
|||||||
size_t curCapacity;
|
size_t curCapacity;
|
||||||
|
|
||||||
if (isLargeOrExternal()) {
|
if (isLargeOrExternal()) {
|
||||||
curData = this->_large.data;
|
curData = _large.data;
|
||||||
curSize = this->_large.size;
|
curSize = _large.size;
|
||||||
curCapacity = this->_large.capacity;
|
curCapacity = _large.capacity;
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
curData = this->_small.data;
|
curData = _small.data;
|
||||||
curSize = this->_small.type;
|
curSize = _small.type;
|
||||||
curCapacity = kSSOCapacity;
|
curCapacity = kSSOCapacity;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,25 +132,20 @@ char* String::prepare(ModifyOp op, size_t size) noexcept {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// Prevent arithmetic overflow.
|
// Prevent arithmetic overflow.
|
||||||
if (ASMJIT_UNLIKELY(size >= kMaxAllocSize - curSize))
|
if (ASMJIT_UNLIKELY(size >= kMaxAllocSize - curSize - 1))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
size_t newSize = size + curSize;
|
size_t newSize = size + curSize;
|
||||||
size_t newSizePlusOne = newSize + 1;
|
size_t newSizePlusOne = newSize + 1;
|
||||||
|
|
||||||
if (newSizePlusOne > curCapacity) {
|
if (newSize > curCapacity) {
|
||||||
size_t newCapacity = Support::max<size_t>(curCapacity + 1, kMinAllocSize);
|
size_t newCapacityPlusOne = String_growCapacity(size + 1u, newSizePlusOne);
|
||||||
|
ASMJIT_ASSERT(newCapacityPlusOne >= newSizePlusOne);
|
||||||
|
|
||||||
if (newCapacity < newSizePlusOne && newCapacity < Globals::kGrowThreshold)
|
if (ASMJIT_UNLIKELY(newCapacityPlusOne < newSizePlusOne))
|
||||||
newCapacity = Support::alignUpPowerOf2(newCapacity);
|
|
||||||
|
|
||||||
if (newCapacity < newSizePlusOne)
|
|
||||||
newCapacity = Support::alignUp(newSizePlusOne, Globals::kGrowThreshold);
|
|
||||||
|
|
||||||
if (ASMJIT_UNLIKELY(newCapacity < newSizePlusOne))
|
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
char* newData = static_cast<char*>(::malloc(newCapacity));
|
char* newData = static_cast<char*>(::malloc(newCapacityPlusOne));
|
||||||
if (ASMJIT_UNLIKELY(!newData))
|
if (ASMJIT_UNLIKELY(!newData))
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
@@ -119,7 +156,7 @@ char* String::prepare(ModifyOp op, size_t size) noexcept {
|
|||||||
|
|
||||||
_large.type = kTypeLarge;
|
_large.type = kTypeLarge;
|
||||||
_large.size = newSize;
|
_large.size = newSize;
|
||||||
_large.capacity = newCapacity - 1;
|
_large.capacity = newCapacityPlusOne - 1;
|
||||||
_large.data = newData;
|
_large.data = newData;
|
||||||
|
|
||||||
newData[newSize] = '\0';
|
newData[newSize] = '\0';
|
||||||
@@ -488,9 +525,28 @@ bool String::equals(const char* other, size_t size) const noexcept {
|
|||||||
// ==============
|
// ==============
|
||||||
|
|
||||||
#if defined(ASMJIT_TEST)
|
#if defined(ASMJIT_TEST)
|
||||||
|
static void test_string_grow() noexcept {
|
||||||
|
String s;
|
||||||
|
size_t c = s.capacity();
|
||||||
|
|
||||||
|
INFO("Testing string grow strategy (SSO capacity: %zu)", c);
|
||||||
|
for (size_t i = 0; i < 1000000; i++) {
|
||||||
|
s.append('x');
|
||||||
|
if (s.capacity() != c) {
|
||||||
|
c = s.capacity();
|
||||||
|
INFO(" String reallocated to new capacity: %zu", c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We don't expect a 1 million character string to occupy 4MiB, for example. So verify that!
|
||||||
|
EXPECT_LT(c, size_t(4 * 1024 * 1024));
|
||||||
|
}
|
||||||
|
|
||||||
UNIT(core_string) {
|
UNIT(core_string) {
|
||||||
String s;
|
String s;
|
||||||
|
|
||||||
|
INFO("Testing string functionality");
|
||||||
|
|
||||||
EXPECT_FALSE(s.isLargeOrExternal());
|
EXPECT_FALSE(s.isLargeOrExternal());
|
||||||
EXPECT_FALSE(s.isExternal());
|
EXPECT_FALSE(s.isExternal());
|
||||||
|
|
||||||
@@ -553,6 +609,8 @@ UNIT(core_string) {
|
|||||||
EXPECT_TRUE(sTmp.isExternal());
|
EXPECT_TRUE(sTmp.isExternal());
|
||||||
EXPECT_EQ(sTmp.appendChars(' ', 1000), kErrorOk);
|
EXPECT_EQ(sTmp.appendChars(' ', 1000), kErrorOk);
|
||||||
EXPECT_FALSE(sTmp.isExternal());
|
EXPECT_FALSE(sTmp.isExternal());
|
||||||
|
|
||||||
|
test_string_grow();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user