-
Notifications
You must be signed in to change notification settings - Fork 178
Optimize size of ArrayDecimal128 #6111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,40 +21,135 @@ | |
|
||
namespace realm { | ||
|
||
namespace { | ||
|
||
uint8_t min_width(const Decimal128& value, bool zero_width_is_zero) | ||
{ | ||
Decimal128::Bid128 coefficient; | ||
int exponent; | ||
bool sign; | ||
|
||
if (value.is_null()) { | ||
return zero_width_is_zero ? 4 : 0; | ||
} | ||
|
||
value.unpack(coefficient, exponent, sign); | ||
if (coefficient.w[1] == 0) { | ||
if (coefficient.w[0] == 0) { | ||
return zero_width_is_zero ? 0 : 4; | ||
} | ||
if (coefficient.w[0] < (1ull << 23) && exponent > -91 && exponent < 91) { | ||
return 4; | ||
} | ||
if (coefficient.w[0] < (1ull << 53) && exponent > -370 && exponent < 370) { | ||
return 8; | ||
} | ||
} | ||
return 16; | ||
} | ||
} // namespace | ||
|
||
void ArrayDecimal128::set(size_t ndx, Decimal128 value) | ||
{ | ||
REALM_ASSERT(ndx < m_size); | ||
copy_on_write(); | ||
auto values = reinterpret_cast<Decimal128*>(m_data); | ||
values[ndx] = value; | ||
switch (upgrade_leaf(min_width(value, Array::get_context_flag()))) { | ||
case 0: | ||
break; | ||
case 4: { | ||
auto values = reinterpret_cast<Decimal::Bid32*>(m_data); | ||
auto val = value.to_bid32(); | ||
REALM_ASSERT(val); | ||
values[ndx] = *val; | ||
break; | ||
} | ||
case 8: { | ||
auto values = reinterpret_cast<Decimal::Bid64*>(m_data); | ||
auto val = value.to_bid64(); | ||
REALM_ASSERT(val); | ||
values[ndx] = *val; | ||
break; | ||
} | ||
case 16: { | ||
auto values = reinterpret_cast<Decimal128*>(m_data); | ||
values[ndx] = value; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
void ArrayDecimal128::insert(size_t ndx, Decimal128 value) | ||
{ | ||
REALM_ASSERT(ndx <= m_size); | ||
if (m_size == 0 && value == Decimal128()) { | ||
// zero width should be interpreted as 0 | ||
Array::copy_on_write(); | ||
Array::set_context_flag(true); | ||
} | ||
// Allocate room for the new value | ||
alloc(m_size + 1, sizeof(Decimal128)); // Throws | ||
switch (upgrade_leaf(min_width(value, Array::get_context_flag()))) { | ||
case 0: | ||
m_size += 1; | ||
Array::copy_on_write(); | ||
set_header_size(m_size); | ||
break; | ||
case 4: { | ||
alloc(m_size + 1, 4); // Throws | ||
|
||
auto src = reinterpret_cast<Decimal128*>(m_data) + ndx; | ||
auto dst = src + 1; | ||
auto src = reinterpret_cast<Decimal::Bid32*>(m_data) + ndx; | ||
auto dst = src + 1; | ||
|
||
// Make gap for new value | ||
memmove(dst, src, sizeof(Decimal128) * (m_size - 1 - ndx)); | ||
// Make gap for new value | ||
memmove(dst, src, sizeof(Decimal::Bid32) * (m_size - 1 - ndx)); | ||
|
||
// Set new value | ||
*src = value; | ||
// Set new value | ||
auto val = value.to_bid32(); | ||
REALM_ASSERT(val); | ||
*src = *val; | ||
break; | ||
} | ||
case 8: { | ||
alloc(m_size + 1, 8); // Throws | ||
|
||
auto src = reinterpret_cast<Decimal::Bid64*>(m_data) + ndx; | ||
auto dst = src + 1; | ||
|
||
// Make gap for new value | ||
memmove(dst, src, sizeof(Decimal::Bid64) * (m_size - 1 - ndx)); | ||
|
||
// Set new value | ||
auto val = value.to_bid64(); | ||
REALM_ASSERT(val); | ||
*src = *val; | ||
break; | ||
} | ||
case 16: { | ||
alloc(m_size + 1, sizeof(Decimal128)); // Throws | ||
|
||
auto src = reinterpret_cast<Decimal128*>(m_data) + ndx; | ||
auto dst = src + 1; | ||
|
||
// Make gap for new value | ||
memmove(dst, src, sizeof(Decimal128) * (m_size - 1 - ndx)); | ||
|
||
// Set new value | ||
*src = value; | ||
break; | ||
} | ||
} | ||
} | ||
|
||
void ArrayDecimal128::erase(size_t ndx) | ||
{ | ||
|
||
REALM_ASSERT(ndx < m_size); | ||
|
||
copy_on_write(); | ||
|
||
Decimal128* dst = reinterpret_cast<Decimal128*>(m_data) + ndx; | ||
Decimal128* src = dst + 1; | ||
|
||
memmove(dst, src, sizeof(Decimal128) * (m_size - ndx)); | ||
if (m_width) { | ||
auto dst = m_data + ndx * m_width; | ||
memmove(dst, dst + m_width, m_width * (m_size - ndx)); | ||
} | ||
|
||
// Update size (also in header) | ||
m_size -= 1; | ||
|
@@ -65,11 +160,19 @@ void ArrayDecimal128::move(ArrayDecimal128& dst_arr, size_t ndx) | |
{ | ||
size_t elements_to_move = m_size - ndx; | ||
if (elements_to_move) { | ||
const auto old_dst_size = dst_arr.m_size; | ||
dst_arr.alloc(old_dst_size + elements_to_move, sizeof(Decimal128)); | ||
Decimal128* dst = reinterpret_cast<Decimal128*>(dst_arr.m_data) + old_dst_size; | ||
Decimal128* src = reinterpret_cast<Decimal128*>(m_data) + ndx; | ||
memmove(dst, src, elements_to_move * sizeof(Decimal128)); | ||
if (m_width >= dst_arr.m_width) { | ||
dst_arr.upgrade_leaf(m_width); | ||
const auto old_dst_size = dst_arr.m_size; | ||
dst_arr.alloc(old_dst_size + elements_to_move, m_width); | ||
auto dst = dst_arr.m_data + old_dst_size * m_width; | ||
auto src = m_data + ndx * m_width; | ||
memmove(dst, src, elements_to_move * m_width); | ||
} | ||
else { | ||
for (size_t i = 0; i < elements_to_move; i++) { | ||
dst_arr.add(get(ndx + i)); | ||
} | ||
} | ||
} | ||
truncate(ndx); | ||
} | ||
|
@@ -81,11 +184,53 @@ size_t ArrayDecimal128::find_first(Decimal128 value, size_t start, size_t end) c | |
end = sz; | ||
REALM_ASSERT(start <= sz && end <= sz && start <= end); | ||
|
||
auto values = reinterpret_cast<Decimal128*>(this->m_data); | ||
for (size_t i = start; i < end; i++) { | ||
if (values[i] == value) | ||
return i; | ||
bool zero_width_is_zero = Array::get_context_flag(); | ||
auto width = min_width(value, zero_width_is_zero); | ||
switch (m_width) { | ||
case 0: | ||
if (zero_width_is_zero) { | ||
if (value == Decimal128()) { | ||
return 0; | ||
} | ||
} | ||
else { | ||
if (value.is_null()) { | ||
return 0; | ||
} | ||
} | ||
break; | ||
case 4: | ||
if (width <= 4) { | ||
// Worth optimizing here | ||
auto optval32 = value.to_bid32(); | ||
REALM_ASSERT(optval32); | ||
auto val32 = *optval32; | ||
auto values = reinterpret_cast<Decimal128::Bid32*>(this->m_data); | ||
for (size_t i = start; i < end; i++) { | ||
if (values[i] == val32) | ||
return i; | ||
} | ||
} | ||
break; | ||
case 8: | ||
if (width <= 8) { | ||
auto values = reinterpret_cast<Decimal128::Bid64*>(this->m_data); | ||
for (size_t i = start; i < end; i++) { | ||
if (Decimal128(values[i]) == value) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can this be done by comparing Bid64 values directly, instead of converting up to Decimal128? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That could be done, but then I would have to implement the comparison function like in the 32 bit case. But I don't think the gain would be that big as it is relatively quick to upgrade. Can be done, if you insist. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not a blocker since it can be optimized later without changing the file format if it actually does show up in performance profiling. |
||
return i; | ||
} | ||
} | ||
break; | ||
case 16: { | ||
auto values = reinterpret_cast<Decimal128*>(this->m_data); | ||
for (size_t i = start; i < end; i++) { | ||
if (values[i] == value) | ||
return i; | ||
} | ||
break; | ||
} | ||
} | ||
|
||
return realm::npos; | ||
} | ||
|
||
|
@@ -95,4 +240,85 @@ Mixed ArrayDecimal128::get_any(size_t ndx) const | |
} | ||
|
||
|
||
size_t ArrayDecimal128::upgrade_leaf(uint8_t width) | ||
{ | ||
if (m_width == 16) { | ||
return 16; | ||
} | ||
if (width <= m_width) { | ||
return m_width; | ||
} | ||
|
||
if (m_size == 0) { | ||
alloc(m_size, width); | ||
return width; | ||
} | ||
|
||
if (m_width == 8) { | ||
// Upgrade to 16 bytes | ||
alloc(m_size, 16); | ||
auto src = reinterpret_cast<Decimal::Bid64*>(m_data); | ||
auto dst = reinterpret_cast<Decimal::Bid128*>(m_data); | ||
for (size_t i = m_size; i > 0; --i) { | ||
auto val = Decimal128(src[i - 1]); | ||
dst[i - 1] = *val.raw(); | ||
} | ||
return 16; | ||
} | ||
|
||
if (m_width == 4) { | ||
alloc(m_size, width); | ||
auto src = reinterpret_cast<Decimal::Bid32*>(m_data); | ||
if (width == 8) { | ||
// Upgrade to 8 bytes | ||
auto dst = reinterpret_cast<Decimal::Bid64*>(m_data); | ||
for (size_t i = m_size; i > 0; --i) { | ||
auto val = Decimal128(src[i - 1]); | ||
dst[i - 1] = *val.to_bid64(); | ||
} | ||
} | ||
else if (width == 16) { | ||
// Upgrade to 16 bytes | ||
auto dst = reinterpret_cast<Decimal::Bid128*>(m_data); | ||
for (size_t i = m_size; i > 0; --i) { | ||
auto val = Decimal128(src[i - 1]); | ||
dst[i - 1] = *val.raw(); | ||
} | ||
} | ||
return width; | ||
} | ||
|
||
// Upgrade from zero width. Fill with either 0 or null. | ||
Decimal128 fill_value = get_context_flag() ? Decimal128(0) : Decimal128(realm::null()); | ||
|
||
if (width == 4) { | ||
// Upgrade to 4 bytes | ||
alloc(m_size, 4); | ||
auto values = reinterpret_cast<Decimal::Bid32*>(m_data); | ||
auto fill = *fill_value.to_bid32(); | ||
for (size_t i = 0; i < m_size; i++) { | ||
values[i] = fill; | ||
} | ||
return 4; | ||
} | ||
else if (width == 8) { | ||
// Upgrade to 8 bytes | ||
alloc(m_size, 8); | ||
auto values = reinterpret_cast<Decimal::Bid64*>(m_data); | ||
auto fill = *fill_value.to_bid64(); | ||
for (size_t i = 0; i < m_size; i++) { | ||
values[i] = fill; | ||
} | ||
return 8; | ||
} | ||
|
||
alloc(m_size, 16); | ||
auto values = reinterpret_cast<Decimal128*>(m_data); | ||
for (size_t i = 0; i < m_size; i++) { | ||
values[i] = fill_value; | ||
} | ||
return 16; | ||
} | ||
|
||
|
||
} // namespace realm |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The existing code is fine, but if you wanted we could even adapt the meaning if changing back and forth (admittedly this would be a rare case and we could do it later if needed).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, m_size would not be zero next time.