LibGfx/JPEGXL: Avoid allocations in the hot path

This was obviously very naive, pre-allocating the array makes it a lot
faster (~1.56 times faster, on the JPEG XL logo image [1]).

Before:
Benchmark 1: image JPEGXL/logo.jxl --no-output
  Time (mean ± σ):     922.2 ms ±  18.4 ms
  Range (min … max):   891.9 ms … 945.4 ms    10 runs

After:
Benchmark 1: image JPEGXL/logo.jxl --no-output
  Time (mean ± σ):     585.4 ms ±  10.0 ms
  Range (min … max):   570.5 ms … 597.9 ms    10 runs

[1] https://jpegxl.info/old/logo.jxl
This commit is contained in:
Lucas CHOLLET authored and Nico Weber committed 2025-03-19 08:07:47 -04:00
1 parent ec83d3aba2
commit abffc2723a
1 file changed
+28 -27
@@ -936,7 +936,7 @@ public:
return tree;
}
LeafNode get_leaf(Vector<i32> const& properties) const
LeafNode get_leaf(Span<i32> properties) const
{
// To find the MA leaf node, the MA tree is traversed, starting at the root node tree[0]
// and for each decision node d, testing if property[d.property] > d.value, proceeding to
@@ -1436,16 +1436,16 @@ struct ModularHeader {
Vector<TransformInfo> transform {};
};
static ErrorOr<Vector<i32>> get_properties(Vector<Channel> const& channels, u16 i, u32 x, u32 y, i32 max_error)
{
Vector<i32> properties;
static constexpr u32 nb_base_predictors = 16;
static void get_properties(FixedArray<i32>& properties, Vector<Channel> const& channels, u16 i, u32 x, u32 y, i32 max_error)
{
// Table H.4 - Property definitions
TRY(properties.try_append(i));
properties[0] = i;
// FIXME: Handle other cases than GlobalModular
TRY(properties.try_append(0));
TRY(properties.try_append(y));
TRY(properties.try_append(x));
properties[1] = 0;
properties[2] = y;
properties[3] = x;
i32 const W = x > 0 ? channels[i].get(x - 1, y) : (y > 0 ? channels[i].get(x, y - 1) : 0);
i32 const N = y > 0 ? channels[i].get(x, y - 1) : W;
@@ -1454,10 +1454,10 @@ static ErrorOr<Vector<i32>> get_properties(Vector<Channel> const& channels, u16
i32 const NN = y > 1 ? channels[i].get(x, y - 2) : N;
i32 const WW = x > 1 ? channels[i].get(x - 2, y) : W;
TRY(properties.try_append(abs(N)));
TRY(properties.try_append(abs(W)));
TRY(properties.try_append(N));
TRY(properties.try_append(W));
properties[4] = abs(N);
properties[5] = abs(W);
properties[6] = N;
properties[7] = W;
// x > 0 ? W - /* (the value of property 9 at position (x - 1, y)) */ : W
if (x > 0) {
@@ -1465,19 +1465,19 @@ static ErrorOr<Vector<i32>> get_properties(Vector<Channel> const& channels, u16
i32 const W_x_1 = x_1 > 0 ? channels[i].get(x_1 - 1, y) : (y > 0 ? channels[i].get(x_1, y - 1) : 0);
i32 const N_x_1 = y > 0 ? channels[i].get(x_1, y - 1) : W_x_1;
i32 const NW_x_1 = x_1 > 0 && y > 0 ? channels[i].get(x_1 - 1, y - 1) : W_x_1;
TRY(properties.try_append(W - (W_x_1 + N_x_1 - NW_x_1)));
properties[8] = W - (W_x_1 + N_x_1 - NW_x_1);
} else {
TRY(properties.try_append(W));
properties[8] = W;
}
TRY(properties.try_append(W + N - NW));
TRY(properties.try_append(W - NW));
TRY(properties.try_append(NW - N));
TRY(properties.try_append(N - NE));
TRY(properties.try_append(N - NN));
TRY(properties.try_append(W - WW));
properties[9] = W + N - NW;
properties[10] = W - NW;
properties[11] = NW - N;
properties[12] = N - NE;
properties[13] = N - NN;
properties[14] = W - WW;
TRY(properties.try_append(max_error));
properties[15] = max_error;
for (i16 j = i - 1; j >= 0; j--) {
if (channels[j].width() != channels[i].width())
@@ -1493,12 +1493,11 @@ static ErrorOr<Vector<i32>> get_properties(Vector<Channel> const& channels, u16
auto rN = (y > 0 ? channels[j].get(x, y - 1) : rW);
auto rNW = (x > 0 && y > 0 ? channels[j].get(x - 1, y - 1) : rW);
auto rG = clamp(rW + rN - rNW, min(rW, rN), max(rW, rN));
TRY(properties.try_append(abs(rC)));
TRY(properties.try_append(rC));
TRY(properties.try_append(abs(rC - rG)));
TRY(properties.try_append(rC - rG));
properties[nb_base_predictors + (i - 1 - j) * 4 + 0] = abs(rC);
properties[nb_base_predictors + (i - 1 - j) * 4 + 1] = rC;
properties[nb_base_predictors + (i - 1 - j) * 4 + 2] = abs(rC - rG);
properties[nb_base_predictors + (i - 1 - j) * 4 + 3] = rC - rG;
}
return properties;
}
static i32 prediction(Neighborhood const& neighborhood, i32 self_correcting, u32 predictor)
@@ -1606,6 +1605,8 @@ static ErrorOr<ModularHeader> read_modular_header(LittleEndianInputBitStream& st
// (in ascending order of index) as specified in H.3, skipping any channels having width or height
// zero. Finally, the inverse transformations are applied (from last to first) as described in H.6.
auto properties = TRY(FixedArray<i32>::create(nb_base_predictors + num_channels * 4));
auto const& tree = local_tree.has_value() ? *local_tree : global_tree;
for (u16 i {}; i < num_channels; ++i) {
@@ -1617,7 +1618,7 @@ static ErrorOr<ModularHeader> read_modular_header(LittleEndianInputBitStream& st
auto const self_prediction = self_correcting_data.compute_predictions(neighborhood, x);
auto const properties = TRY(get_properties(image.channels(), i, x, y, self_prediction.max_error));
get_properties(properties, image.channels(), i, x, y, self_prediction.max_error);
auto const leaf_node = tree.get_leaf(properties);
auto diff = unpack_signed(TRY(decoder->decode_hybrid_uint(stream, leaf_node.ctx)));
diff = (diff * leaf_node.multiplier) + leaf_node.offset;