libwebp2: Upstream sync

Squashed commits:
6ef184b369a96cbf49ac7b46d973ac31daaac311 rework Quantizer::Config
6ca8d0e4fa7d1bdbdd16b526aac2af926bd16af7 Small Quantizer refactors.
6e977b6b4499d4315ee15b2fd0734d564b532163 Fix quantizer memory corruption.
238a52b64601f1168ab70c2f2276f4d04627a6da wp2: Remove YModePredictor predictor
56460010c1a736e611957e2fce238960575e2fd2 FrontMgr refactor
fc360e8638a56f2dce21193d60045ca9e9d2e9bc Fix cost estimations in WriteHeaders
3f7117244aa3137c96c0bfc0c64d17f8c7be9ee0 Send the effort parameter to the Quantizer.

Change-Id: I1e5605ace78c670c4d63ce657ff64eb19776bd70
Reviewed-on: https://chromium-review.googlesource.com/c/codecs/libwebp2/+/2643497
Reviewed-by: Pascal Massimino <skal@google.com>
Tested-by: Pascal Massimino <skal@google.com>
Verified-Ubuntu: Yannis Guyon <yguyon@google.com>
diff --git a/src/common/lossy/block.cc b/src/common/lossy/block.cc
index e9022c6..c73aed8 100644
--- a/src/common/lossy/block.cc
+++ b/src/common/lossy/block.cc
@@ -799,17 +799,12 @@
   if (channel == kUChannel) pred_scores_[kVChannel] = pred_scores_[kUChannel];
 }
 
-WP2Status CodedBlock::PredictorRate(
-    const YModePredictor* const ymode_predictor, Channel channel,
-    SymbolCounter* const counter, float* const rate) const {
+WP2Status CodedBlock::PredictorRate(Channel channel,
+                                    SymbolCounter* const counter,
+                                    float* const rate) const {
   counter->Clear();
   ANSEncCounter enc;
-  if (channel == kYChannel || channel == kAChannel) {
-    SyntaxWriter::WriteYAPredictors(*this, channel, ymode_predictor,
-                                    /*update_ymodes=*/false, counter, &enc);
-  } else {
-    SyntaxWriter::WriteUVPredictors(*this, channel, counter, &enc);
-  }
+  SyntaxWriter::WritePredictors(*this, channel, counter, &enc);
   *rate = enc.GetCost();
   return WP2_STATUS_OK;
 }
@@ -840,8 +835,7 @@
   WP2_CHECK_STATUS(ResidualRate(context, channel, num_channels,
                                 counters->residuals(),
                                 counters->residuals_aom(), res_rate));
-  WP2_CHECK_STATUS(PredictorRate(is_uv ? nullptr : &context.ymodes(), channel,
-                                 counters->predictor(), pred_rate));
+  WP2_CHECK_STATUS(PredictorRate(channel, counters->predictor(), pred_rate));
 
   const float rate = *res_rate + *pred_rate + tf_rate;
   *score =
diff --git a/src/common/lossy/block.h b/src/common/lossy/block.h
index e9e7c8a..7780e56 100644
--- a/src/common/lossy/block.h
+++ b/src/common/lossy/block.h
@@ -75,32 +75,25 @@
 
 //------------------------------------------------------------------------------
 // Context passed around and updated once we know the right parameters for each
-// block. Only contains AOM context for now.
+// block. Currently quite empty but can be removed or extended when needed.
 
 class BlockContext {
  public:
-  // 'use_aom' defines the usage of AOM residuals. 'width' and 'height' are in
-  // pixels.
-  WP2Status Init(bool use_aom, uint32_t width, uint32_t height) {
+  // 'use_aom' defines the usage of AOM residuals.
+  WP2Status Init(bool use_aom) {
     use_aom_ = use_aom;
-    WP2_CHECK_STATUS(modes_.InitMap(width, height));
     return WP2_STATUS_OK;
   }
   WP2Status CopyFrom(const BlockContext& other) {
     use_aom_ = other.use_aom_;
-    WP2_CHECK_STATUS(modes_.CopyFrom(other.modes_));
     return WP2_STATUS_OK;
   }
   void Reset() {
-    modes_.Reset();
   }
   bool use_aom() const { return use_aom_; }
-  const YModePredictor& ymodes() const { return modes_; }
-  YModePredictor& ymodes() { return modes_; }
 
  private:
   bool use_aom_ = false;
-  YModePredictor modes_;
 };
 
 //------------------------------------------------------------------------------
@@ -467,8 +460,7 @@
 
   // Returns an estimation of the number of bits necessary to encode the
   // predictor modes.
-  WP2Status PredictorRate(const YModePredictor* const ymode_predictor,
-                          Channel channel, SymbolCounter* const counter,
+  WP2Status PredictorRate(Channel channel, SymbolCounter* const counter,
                           float* const rate) const;
 
   // Returns an estimation of the number of bits necessary to encode the
diff --git a/src/common/lossy/context.cc b/src/common/lossy/context.cc
index 07b7834..42189f1 100644
--- a/src/common/lossy/context.cc
+++ b/src/common/lossy/context.cc
@@ -297,58 +297,6 @@
 }
 
 //------------------------------------------------------------------------------
-// LargeModePredictor
-
-WP2Status YModePredictor::InitMap(uint32_t width, uint32_t) {
-  const uint32_t map_width = SizeBlocks(width);
-  WP2_CHECK_ALLOC_OK(map_.resize(map_width * kMapHeight));
-  predictor_ctxt_.Init(&map_, map_width, kMapHeight);
-  Reset();
-  return WP2_STATUS_OK;
-}
-
-WP2Status YModePredictor::CopyFrom(const YModePredictor& other) {
-  WP2_CHECK_ALLOC_OK(map_.copy_from(other.map_));
-  const uint32_t map_width = (uint32_t)map_.size() / kMapHeight;
-  assert(map_width * kMapHeight == other.map_.size());
-  predictor_ctxt_.Init(&map_, map_width, kMapHeight);
-  // Nothing else to copy in 'predictor_ctxt_' as it only points to 'map_'.
-  return WP2_STATUS_OK;
-}
-
-void YModePredictor::Reset() { std::fill(map_.begin(), map_.end(), -1); }
-
-static uint32_t GetCluster(const ContextImpl<int8_t>& ctxt,
-                           const CodedBlock& cb) {
-  const int above_mode = (cb.y() > 0) ? ctxt.Get(cb.x(), cb.y() - 1) : 0;
-  const int left_mode = (cb.x() > 0) ? ctxt.Get(cb.x() - 1, cb.y()) : 0;
-  return (above_mode == left_mode && above_mode >= 0)
-             ? above_mode
-             : (uint32_t)Predictor::ModeContext::kNum;
-}
-
-void YModePredictor::Write(const CodedBlock& cb, const Predictor& pred,
-                           bool do_update, ANSEncBase* const enc,
-                           SymbolManager* const sw) {
-  const uint32_t cluster = GetCluster(predictor_ctxt_, cb);
-  sw->Process(kSymbolModeY, cluster, pred.mode(), "predictor", enc);
-  if (do_update) {
-    const Predictor::ModeContext ctxt = pred.mode_context();
-    predictor_ctxt_.Set(cb.blk(), (int)ctxt);
-  }
-}
-
-uint32_t YModePredictor::Read(const CodedBlock& cb, const Predictors& preds,
-                              SymbolReader* const sr) {
-  const uint32_t cluster = GetCluster(predictor_ctxt_, cb);
-  const uint32_t mode = sr->Read(kSymbolModeY, cluster, "predictor", nullptr);
-  const Predictor::ModeContext ctxt =
-      preds.GetFirstWithMode(mode)->mode_context();
-  predictor_ctxt_.Set(cb.blk(), (int)ctxt);
-  return mode;
-}
-
-//------------------------------------------------------------------------------
 // AlphaModePredictor
 //------------------------------------------------------------------------------
 
diff --git a/src/common/lossy/context.h b/src/common/lossy/context.h
index 18a4388..074efd6 100644
--- a/src/common/lossy/context.h
+++ b/src/common/lossy/context.h
@@ -227,34 +227,6 @@
 };
 
 //------------------------------------------------------------------------------
-// Mode-predictor predictor (yes ...).
-
-class Predictor;
-class Predictors;
-// Predicts the predictor of a block, based on the predictor modes around that
-// block. The logic is different from AV1 where a CDF is chosen in a 2d table
-// based on the mode above and on the left. Here, if the above and left modes
-// are the same, we choose a CDF based on the mode, otherwise we choose the
-// generic CDF.
-class YModePredictor {
- public:
-  YModePredictor() = default;
-
-  // Will allocate the internal working map_[]. width and height in pixels.
-  WP2Status InitMap(uint32_t width, uint32_t height);
-  WP2Status CopyFrom(const YModePredictor& other);
-  void Reset();
-  void Write(const CodedBlock& cb, const Predictor& pred, bool do_update,
-             ANSEncBase* const enc, SymbolManager* const sw);
-  uint32_t Read(const CodedBlock& cb, const Predictors& preds,
-                SymbolReader* const sr);
-
- private:
-  Vector_s8 map_;
-  ContextImpl<int8_t> predictor_ctxt_;
-};
-
-//------------------------------------------------------------------------------
 // Alpha mode predictor.
 
 class AlphaModePredictor {
diff --git a/src/common/lossy/predictor.cc b/src/common/lossy/predictor.cc
index 6eda268..aa97f00 100644
--- a/src/common/lossy/predictor.cc
+++ b/src/common/lossy/predictor.cc
@@ -196,7 +196,6 @@
  public:
   enum Type { kAll, kLeft, kTop };
   explicit LargeDCPredictor(Type type) : type_(type) {}
-  ModeContext mode_context() const override { return ModeContext::kDC; }
 
   std::string GetName() const override {
     const char* const kTypeStr[]{"all", "left", "top"};
@@ -228,10 +227,6 @@
  public:
   TMPredictor(int16_t min_value, int16_t max_value)
       : min_value_(min_value), max_value_(max_value) {}
-  ModeContext mode_context() const override {
-    // Just like 135 degrees.
-    return ModeContext::kLeft;
-  }
 
   std::string GetName() const override { return "TM predictor"; }
   void Draw(const WP2::Rectangle& rect,
@@ -262,15 +257,6 @@
     kNumSmoothType
   };
   explicit SmoothPredictor(SmoothType type) : type_(type) {}
-  ModeContext mode_context() const override {
-    if (type_ == SmoothType::k2DSmooth) {
-      return ModeContext::kDC;
-    } else if (type_ == SmoothType::kVerticalSmooth) {
-      return ModeContext::kVertical;
-    } else {
-      return ModeContext::kHorizontal;
-    }
-  }
 
   std::string GetName() const override {
     const char* const kTypeStr[]{"2D", "vertical", "horizontal"};
@@ -336,24 +322,6 @@
     context_type_ = GetContextType(angle_idx_);
   }
 
-  ModeContext mode_context() const override {
-    if (type_ == Type::D23_PRED || type_ == Type::D45_PRED ||
-        type_ == Type::D67_PRED) {
-      return ModeContext::kTopRight;
-    } else if (type_ == Type::V_PRED) {
-      return ModeContext::kVertical;
-    } else if (type_ == Type::D113_PRED || type_ == Type::D135_PRED ||
-               type_ == Type::D157_PRED || type_ == Type::D203_PRED ||
-               type_ == Type::D225_PRED) {
-      return ModeContext::kLeft;
-    } else if (type_ == Type::H_PRED) {
-      return ModeContext::kHorizontal;
-    } else {
-      assert(false);
-    }
-    return ModeContext::kDC;
-  }
-
   // Returns the angle index for precalculated angles.
   // Index goes from 0 (12.86 degrees) to 69 (234.64 degrees).
   static uint8_t AngleIdx(Type type, Channel channel, int16_t angle_step) {
@@ -452,10 +420,6 @@
       : strength_(strength), min_value_(min_value), max_value_(max_value) {
     PrecomputeLargeWeightTable(strength_, table_);
   }
-  ModeContext mode_context() const override {
-    // TODO(vrabaud) find the right one.
-    return ModeContext::kDC;
-  }
 
   std::string GetName() const override {
     std::string name = "fuse predictor (strength: ";
@@ -489,11 +453,6 @@
  public:
   GradientPredictor(int16_t min_value, int16_t max_value)
       : min_value_(min_value), max_value_(max_value) {}
-  ModeContext mode_context() const override {
-    // TODO(vrabaud) find the right one.
-    // Just like 135 degrees.
-    return ModeContext::kLeft;
-  }
 
   std::string GetName() const override { return "gradient predictor"; }
   void Draw(const WP2::Rectangle& rect,
diff --git a/src/common/lossy/predictor.h b/src/common/lossy/predictor.h
index 16f021e..e08bb22 100644
--- a/src/common/lossy/predictor.h
+++ b/src/common/lossy/predictor.h
@@ -58,18 +58,6 @@
 // heuristics and local context information.
 class Predictor : public WP2Allocable {
  public:
-  // Context of the prediction: each predictor can belong to a context (mainly
-  // horizontal, mainly vertical ...). AV1 defines the mapping between the
-  // predictor and a mode in Intra_Mode_Context.
-  enum class ModeContext {
-    kDC,
-    kVertical,
-    kHorizontal,
-    kTopRight,
-    kLeft,  // for left angle predictors that are not horizontal.
-    kNum
-  };
-
   virtual ~Predictor() = default;
 
   // Predicts values for the given block, on channel 'channel', for the
@@ -118,7 +106,6 @@
   // predictors.
   void SetMode(uint32_t mode) { mode_ = mode; }
   virtual uint32_t mode() const { return mode_; }
-  virtual ModeContext mode_context() const { return ModeContext::kNum; }
 
   // Debugging methods.
   virtual std::string GetName() const = 0;
diff --git a/src/common/symbols.cc b/src/common/symbols.cc
index a85ceb3..c7a53ca 100644
--- a/src/common/symbols.cc
+++ b/src/common/symbols.cc
@@ -214,10 +214,8 @@
                                  uint32_t quality_hint, bool use_aom_coeffs) {
   const uint32_t num_channels = (has_alpha ? 4 : 3);
 
-  // kNum is used as an escape code when the upper/left mode do not match.
   SetInfo(kSymbolModeY, /*min=*/0, /*max=*/kYPredModeNum - 1,
-          (uint32_t)Predictor::ModeContext::kNum + 1,
-          StorageMethod::kAdaptiveSym);
+          /*num_clusters=*/1, StorageMethod::kAdaptiveSym);
   SetInfo(kSymbolModeUV, /*min=*/0, /*max=*/kUVPredModeNum - 1,
           /*num_clusters=*/1, StorageMethod::kAdaptiveWithAutoSpeed);
   SetInfo(kSymbolSegmentId, /*min=*/0, /*max=*/num_segments - 1,
diff --git a/src/common/symbols.h b/src/common/symbols.h
index d3c7367..e84f103 100644
--- a/src/common/symbols.h
+++ b/src/common/symbols.h
@@ -322,11 +322,6 @@
 class SymbolIO {
  public:
   virtual ~SymbolIO() = default;
-  // Sets basic information about symbols.
-  WP2Status Init(const SymbolsInfo& symbols_info) {
-    WP2_CHECK_STATUS(symbols_info_.CopyFrom(symbols_info));
-    return WP2_STATUS_OK;
-  }
 
   // Allocates data after ranges/cluster sizes have been specified.
   virtual WP2Status Allocate() {
@@ -405,6 +400,12 @@
     Param param;
     EXTRA extra;
   };
+  // Sets basic information about symbols.
+  WP2Status Init(const SymbolsInfo& symbols_info) {
+    WP2_CHECK_STATUS(symbols_info_.CopyFrom(symbols_info));
+    return WP2_STATUS_OK;
+  }
+
   // Gets the statistics for symbol 'sym' in cluster 'cluster'.
   inline Stat* GetStats(uint32_t sym, uint32_t cluster) {
     assert(sym < symbols_info_.Size() &&
diff --git a/src/dec/syntax_dec.cc b/src/dec/syntax_dec.cc
index 280ed06..bd35b5f 100644
--- a/src/dec/syntax_dec.cc
+++ b/src/dec/syntax_dec.cc
@@ -47,14 +47,13 @@
 
   ANSDebugPrefix prefix(dec_, "GlobalHeader");
 
-  use_aom_coeffs_ = dec_->ReadBool("use_aom_coeffs");
-  WP2_CHECK_STATUS(context_.Init(use_aom_coeffs_, width_, height_));
-  residual_reader_.Init(use_aom_coeffs_);
+  WP2_CHECK_STATUS(context_.Init(dec_->ReadBool("use_aom_coeffs")));
+  residual_reader_.Init(context_.use_aom());
 
   WP2_CHECK_STATUS(symbols_info_.InitLossy(
       (uint32_t)gparams_->segments_.size(), gparams.partition_set_,
       gparams_->maybe_use_lossy_alpha_, features.quality_hint,
-      use_aom_coeffs_));
+      context_.use_aom()));
   const uint32_t num_channels = (gparams_->maybe_use_lossy_alpha_ ? 4 : 3);
   for (Channel c : {kYChannel, kUChannel, kVChannel, kAChannel}) {
     if (c == kAChannel && !gparams_->maybe_use_lossy_alpha_) continue;
@@ -157,20 +156,15 @@
   ANSDebugPrefix prefix(dec_, "pred_modes");
   cb->y_context_is_constant_ = cb->ContextIsConstant(kYChannel);
   for (Channel channel : {kYChannel, kAChannel}) {
-    ANSDebugPrefix prefix2(dec_, (channel == kYChannel) ? "y" : "a");
     if (channel == kAChannel && !cb->HasLossyAlpha()) continue;
     if (channel == kYChannel && cb->y_context_is_constant_) {
       cb->SetLumaUniformPredictor(gparams_->y_preds_);
     } else {
       CodedBlock::CodingParams* const params = cb->GetCodingParams(channel);
       const Predictors& preds = gparams_->predictors(channel);
-      uint32_t mode;
-      if (channel == kYChannel) {
-        mode = context_.ymodes().Read(*cb, preds, &sr_);
-      } else {
-        assert(channel == kAChannel);
-        mode = sr_.Read(kSymbolModeA, "pred");
-      }
+      const uint32_t mode = (channel == kYChannel)
+                                ? sr_.Read(kSymbolModeY, "y")
+                                : sr_.Read(kSymbolModeA, "a");
       preds.GetFirstWithMode(mode)->ReadParams(cb, channel, &sr_, dec_);
       params->pred = preds.GetWithMode(mode, params->pred_sub_mode);
     }
@@ -287,7 +281,7 @@
   dec_->PopBitTracesCustomPrefix("transforms");
 
   // Signal the method with which coeffs were encoded.
-  if (!use_aom_coeffs_) {
+  if (!context_.use_aom()) {
     ANSDebugPrefix prefix(dec_, "coeff_method");
     const Segment& segment = gparams_->segments_[cb->id_];
     WP2_CHECK_STATUS(segment.ReadEncodingMethods(&sr_, cb));
diff --git a/src/dec/wp2_dec_i.h b/src/dec/wp2_dec_i.h
index c9c7b70..21a1687 100644
--- a/src/dec/wp2_dec_i.h
+++ b/src/dec/wp2_dec_i.h
@@ -269,7 +269,6 @@
   BlockContext context_;
   uint32_t width_;
   uint32_t height_;
-  bool use_aom_coeffs_;
 
  public:
   // Visual debug: stores bit cost for a block
diff --git a/src/enc/lossless/backward_references_enc.cc b/src/enc/lossless/backward_references_enc.cc
index ba843ec..28169e4 100644
--- a/src/enc/lossless/backward_references_enc.cc
+++ b/src/enc/lossless/backward_references_enc.cc
@@ -739,10 +739,10 @@
     // Get the header cost in bits.
     WP2::ANSEncCounter enc_counter;
     WP2::SymbolWriter symbol_writer;
-    WP2_CHECK_STATUS(symbol_writer.Init(info));
+    WP2_CHECK_STATUS(symbol_writer.Init(info, effort));
     WP2_CHECK_STATUS(symbol_writer.Allocate());
     WP2::ANSDictionaries dicts;
-    WP2_CHECK_STATUS(WriteHeaders(symbol_recorder, info, num_pixels,
+    WP2_CHECK_STATUS(WriteHeaders(symbol_recorder, info, num_pixels, effort,
                                   &enc_counter, &dicts, &symbol_writer));
     const float cost_header = enc_counter.GetCost();
 
@@ -816,7 +816,7 @@
 // Gets the overall cost of storing the refs: symbol headers and pixels.
 static WP2Status GetStorageCost(uint32_t width, const BackwardRefs& refs,
                                 const LosslessSymbolsInfo& symbols_info,
-                                uint32_t num_pixels,
+                                uint32_t num_pixels, int effort,
                                 float* const cost) {
   // Get the symbols stats.
   WP2::SymbolRecorder recorder;
@@ -828,7 +828,7 @@
   WP2::ANSEncCounter enc_counter;
   WP2::ANSDictionaries dicts;
   WP2::SymbolWriter symbol_writer;
-  WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info, num_pixels,
+  WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info, num_pixels, effort,
                                 &enc_counter, &dicts, &symbol_writer));
   WP2_CHECK_STATUS(StorePixels(width, refs, &enc_counter, &symbol_writer));
   *cost = enc_counter.GetCost();
@@ -910,7 +910,7 @@
 
     float bit_cost;
     WP2_CHECK_STATUS(GetStorageCost(width, *refs_tmp, symbols_info_tmp,
-                                    num_pixels, &bit_cost));
+                                    num_pixels, effort, &bit_cost));
 
     // Improve on simple LZ77 but only for high effort (TraceBackwards is
     // costly).
@@ -927,7 +927,7 @@
         assert(refs_tmp_trace->IsValid(num_pixels));
         float bit_cost_trace;
         WP2_CHECK_STATUS(GetStorageCost(width, *refs_tmp_trace,
-                                        symbols_info_tmp, num_pixels,
+                                        symbols_info_tmp, num_pixels, effort,
                                         &bit_cost_trace));
         if (bit_cost_trace < bit_cost) {
           bit_cost = bit_cost_trace;
diff --git a/src/enc/lossless/group4_enc.cc b/src/enc/lossless/group4_enc.cc
index 3f515c1..f74fd02 100644
--- a/src/enc/lossless/group4_enc.cc
+++ b/src/enc/lossless/group4_enc.cc
@@ -230,7 +230,7 @@
 
 WP2Status Group4Encode(const int16_t* const argb, uint32_t width,
                        uint32_t height, uint32_t num_colors,
-                       bool use_move_to_front,
+                       bool use_move_to_front, int effort,
                        const WP2::ProgressRange& progress,
                        WP2::ANSEncBase* const enc,
                        EncodeInfo* const encode_info) {
@@ -253,7 +253,7 @@
 
   // Write the headers.
   WP2::SymbolWriter sw;
-  WP2_CHECK_STATUS(sw.Init(info));
+  WP2_CHECK_STATUS(sw.Init(info, effort));
   WP2_CHECK_STATUS(sw.Allocate());
 
   enc->AddDebugPrefix("GlobalHeader");
diff --git a/src/enc/lossless/losslessi_enc.cc b/src/enc/lossless/losslessi_enc.cc
index 7ee81bd..40521ae 100644
--- a/src/enc/lossless/losslessi_enc.cc
+++ b/src/enc/lossless/losslessi_enc.cc
@@ -195,11 +195,12 @@
 // in the A,R,B channels.
 WP2Status WriteHeaders(const WP2::SymbolRecorder& recorder,
                        const LosslessSymbolsInfo& symbols_info,
-                       uint32_t num_pixels, WP2::ANSEncBase* const enc,
+                       uint32_t num_pixels, int effort,
+                       WP2::ANSEncBase* const enc,
                        WP2::ANSDictionaries* const dicts,
                        WP2::SymbolWriter* const sw) {
   WP2::ANSDebugPrefix prefix(enc, "symbols");
-  WP2_CHECK_STATUS(sw->Init(symbols_info));
+  WP2_CHECK_STATUS(sw->Init(symbols_info, effort));
   WP2_CHECK_STATUS(sw->Allocate());
 
   // Iterate over all histograms and get the aggregate number of codes used.
@@ -257,8 +258,8 @@
   WP2_CHECK_STATUS(StorePixels(width, *refs, &noop, &recorder));
 
   // Store headers.
-  WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info_new, width * height, enc,
-                                dicts, &sw));
+  WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info_new, width * height,
+                                effort, enc, dicts, &sw));
 
   // Store actual literals.
   WP2_CHECK_STATUS(StorePixels(width, height, /*histo_bits=*/0, *refs, progress,
@@ -386,8 +387,8 @@
                                    &enc_noop, &recorder));
 
       // Store symbol headers.
-      WP2_CHECK_STATUS(
-          WriteHeaders(recorder, symbols_info, num_pixels, enc, dicts, &sw));
+      WP2_CHECK_STATUS(WriteHeaders(recorder, symbols_info, num_pixels, effort,
+                                    enc, dicts, &sw));
       WP2_CHECK_STATUS(header_progress.AdvanceBy(1.));
     }
 
@@ -803,8 +804,8 @@
     if (use_group4) {
       WP2_CHECK_STATUS(
           Group4Encode(encoder->argb_, width, height, encoder->palette_.Size(),
-                       config->group4_use_move_to_front, encode_progress, &enc,
-                       &current_encode_info));
+                       config->group4_use_move_to_front, effort,
+                       encode_progress, &enc, &current_encode_info));
     } else {
       // If using a color cache, do not have it bigger than the number of
       // colors.
diff --git a/src/enc/lossless/losslessi_enc.h b/src/enc/lossless/losslessi_enc.h
index 37bd8f2..794039e 100644
--- a/src/enc/lossless/losslessi_enc.h
+++ b/src/enc/lossless/losslessi_enc.h
@@ -134,7 +134,8 @@
 // info.
 WP2Status WriteHeaders(const WP2::SymbolRecorder& recorder,
                        const LosslessSymbolsInfo& symbols_info,
-                       uint32_t num_pixels, WP2::ANSEncBase* const enc,
+                       uint32_t num_pixels, int effort,
+                       WP2::ANSEncBase* const enc,
                        WP2::ANSDictionaries* const dicts,
                        WP2::SymbolWriter* const sw);
 
@@ -180,7 +181,7 @@
 
 WP2Status Group4Encode(const int16_t* const argb, uint32_t width,
                        uint32_t height, uint32_t num_colors,
-                       bool use_move_to_front,
+                       bool use_move_to_front, int effort,
                        const WP2::ProgressRange& progress,
                        WP2::ANSEncBase* const enc,
                        EncodeInfo* const encode_info);
diff --git a/src/enc/lossy_enc.cc b/src/enc/lossy_enc.cc
index 8434a9a..33e7ca3 100644
--- a/src/enc/lossy_enc.cc
+++ b/src/enc/lossy_enc.cc
@@ -137,24 +137,25 @@
   const CSPTransform& transf = gparams.transf_;
 
   // First, extract partitioning.
-  Vector<CodedBlock> cblocks;
+  VectorNoCtor<Block> blocks;
+  WP2_CHECK_ALLOC_OK(blocks.resize(forced_partition.size()));
+  std::copy(forced_partition.begin(), forced_partition.end(), blocks.begin());
+  WP2_CHECK_STATUS(ExtractBlockPartition(*config_, gparams, yuv_buffer,
+                                         tile_rect, partitioning_progress,
+                                         &blocks));
   FrontMgrDefault mgr;
   WP2_CHECK_STATUS(mgr.Init(config_->partition_set, config_->partition_snapping,
                             padded_rect.width, padded_rect.height));
-  WP2_CHECK_ALLOC_OK(mgr.Blocks().resize(forced_partition.size()));
-  std::copy(forced_partition.begin(), forced_partition.end(),
-            mgr.Blocks().begin());
-  WP2_CHECK_STATUS(ExtractBlockPartition(*config_, gparams, yuv_buffer,
-                                         tile_rect, partitioning_progress,
-                                         &mgr.Blocks()));
-  WP2_CHECK_STATUS(mgr.Sort());
+
+  Vector_u16 size_order_indices;
+  WP2_CHECK_STATUS(mgr.Sort(blocks, size_order_indices));
   // process all blocks
-  WP2_CHECK_ALLOC_OK(cblocks.resize(mgr.Blocks().size()));
 
   // Setup the top-level coding info for each cblock
-  mgr.Clear();
-  for (uint32_t i = 0; i < mgr.Blocks().size(); ++i) {
-    const auto& blk = mgr.Blocks()[i];
+  Vector<CodedBlock> cblocks;
+  WP2_CHECK_ALLOC_OK(cblocks.resize(blocks.size()));
+  for (uint32_t i = 0; i < blocks.size(); ++i) {
+    const auto& blk = blocks[i];
     CodedBlock& cb = cblocks[i];
     cb.SetRange(transf.GetYUVMin(), transf.GetYUVMax());
     cb.SetDim(blk, mgr);
@@ -349,8 +350,8 @@
     // Sizes might be written in a different order than the blocks, hence a
     // separate recording: we need to follow the SizeIndices() order.
     mgr.Clear();
-    for (uint16_t ind : mgr.SizeIndices()) {
-      const auto& block = mgr.Blocks()[ind];
+    for (uint16_t ind : size_order_indices) {
+      const auto& block = blocks[ind];
       WP2_CHECK_STATUS(writer.RecordSize(mgr, block.dim()));
       Block block_tmp;
       WP2_CHECK_ALLOC_OK(mgr.UseSize(block.dim(), ind, &block_tmp));
@@ -368,7 +369,7 @@
   WP2_CHECK_STATUS(writer.WriteHeader(enc));
 
   // Write coded blocks
-  WP2_CHECK_STATUS(writer.WriteBlocks(cblocks, &mgr, enc));
+  WP2_CHECK_STATUS(writer.WriteBlocks(cblocks, size_order_indices, &mgr, enc));
   WP2_CHECK_STATUS(write_progress.AdvanceBy(1.));
 
 #if !defined(WP2_REDUCE_BINARY_SIZE)
diff --git a/src/enc/partitioning/partition_score_func_area.cc b/src/enc/partitioning/partition_score_func_area.cc
index c330a80..2a7fcd8 100644
--- a/src/enc/partitioning/partition_score_func_area.cc
+++ b/src/enc/partitioning/partition_score_func_area.cc
@@ -95,7 +95,7 @@
   WP2_CHECK_STATUS(syntax_writer_.SetInitialSegmentIds());
   WP2_CHECK_STATUS(syntax_writer_.InitPass());
 
-  WP2_CHECK_STATUS(context_.Init(use_aom_coeffs, yuv.Y.w_, yuv.Y.h_));
+  WP2_CHECK_STATUS(context_.Init(use_aom_coeffs));
 
   if (DCDiffusionMap::GetDiffusion(config_->error_diffusion) > 0) {
     WP2_CHECK_STATUS(dc_error_u_.Init(tile_rect_.width));
diff --git a/src/enc/partitioning/partition_score_func_block.cc b/src/enc/partitioning/partition_score_func_block.cc
index c6aca7d..794a45b 100644
--- a/src/enc/partitioning/partition_score_func_block.cc
+++ b/src/enc/partitioning/partition_score_func_block.cc
@@ -70,7 +70,7 @@
   }
 
   // Initialize the cache.
-  WP2_CHECK_STATUS(context_.Init(use_aom_coeffs, yuv.Y.w_, yuv.Y.h_));
+  WP2_CHECK_STATUS(context_.Init(use_aom_coeffs));
   WP2_CHECK_STATUS(progress.AdvanceBy(0.5));
   return WP2_STATUS_OK;
 }
diff --git a/src/enc/symbols_enc.cc b/src/enc/symbols_enc.cc
index 68da9b6..0a5e5e7 100644
--- a/src/enc/symbols_enc.cc
+++ b/src/enc/symbols_enc.cc
@@ -482,6 +482,7 @@
   // No need to copy Quantizer instances. They only contain data members to
   // avoid reallocations. TODO(yguyon): Check if more could be skipped
   WP2_CHECK_ALLOC_OK(stats_buffer_.copy_from(other.stats_buffer_));
+  effort_ = other.effort_;
   return WP2_STATUS_OK;
 }
 
@@ -836,15 +837,15 @@
   }
   // Quantize the histogram to get the cost of using a dictionary.
   quantizer_golomb_.Quantize(histogram_golomb_.data(), mapping_golomb_.data(),
-                             size_golomb, range_golomb, max_nnz,
-                             /*effort=*/9, config_golomb);
+                             size_golomb, range_golomb, max_nnz, effort_,
+                             config_golomb);
   (*config_golomb)->param.golomb_size = size_golomb;
   (*config_golomb)->param.golomb_prefix_size = prefix_size;
   *cost += (*config_golomb)->cost;
   // Do not use Golomb if there is only one value for now.
   const uint32_t nnz_range_golomb = std::min(max_nnz, range_golomb);
   // Add the prefix_size and size cost.
-  *cost += 1.f + WP2Log2(nnz_range_golomb - 1);
+  *cost += 1.f + WP2Log2(nnz_range_golomb - 2);
 }
 
 WP2Status SymbolWriter::WriteHeader(uint32_t sym, uint32_t cluster,
@@ -1041,9 +1042,9 @@
   if (size < ANS_MAX_SYMBOLS) {
     // Quantize the histogram to get the cost of using a dictionary.
     quantizer_.Quantize(histogram_.data(), mapping_.data(), size, range,
-                        max_nnz, /*effort=*/9, &config);
+                        max_nnz, effort_, &config);
     cost_dict = config->cost;
-    cost_dict += WP2Log2(nnz_range);  // Add the size cost.
+    cost_dict += WP2Log2(nnz_range - 2);  // Add the size cost.
   } else {
     cost_dict = std::numeric_limits<float>::max();
   }
diff --git a/src/enc/symbols_enc.h b/src/enc/symbols_enc.h
index bf6571e..92edc15 100644
--- a/src/enc/symbols_enc.h
+++ b/src/enc/symbols_enc.h
@@ -215,6 +215,13 @@
                      public SymbolIO<SymbolWriterStatExtra>,
                      public WP2Allocable {
  public:
+  // Sets basic information about symbols.
+  WP2Status Init(const SymbolsInfo& symbols_info, int effort) {
+    WP2_CHECK_STATUS(SymbolIO<SymbolWriterStatExtra>::Init(symbols_info));
+    effort_ = effort;
+    return WP2_STATUS_OK;
+  }
+
   const SymbolsInfo& symbols_info() const override { return symbols_info_; }
 
   // Allocates memory. Should be called after calling SymbolIO::Init().
@@ -313,6 +320,7 @@
   Quantizer quantizer_;
   Quantizer quantizer_golomb_;
   VectorNoCtor<OptimizeArrayStorageStat> stats_buffer_;
+  int effort_;
 };
 
 }  // namespace WP2
diff --git a/src/enc/syntax_enc.cc b/src/enc/syntax_enc.cc
index ebb77b8..901926b 100644
--- a/src/enc/syntax_enc.cc
+++ b/src/enc/syntax_enc.cc
@@ -41,16 +41,17 @@
   tile_rect_ = tile_rect;
   gparams_ = &gparams;
   assert(gparams_->IsOk());
-  use_aom_coeffs_ = use_aom_coeffs;
   chroma_subsampling_ = chroma_subsampling;
 
   num_blocks_ = num_blocks;
   // Number of transforms is unknown yet, use a higher bound.
   num_transforms_ = std::min(
       num_blocks * 4, SizeBlocks(yuv.GetWidth()) * SizeBlocks(yuv.GetHeight()));
+  // Deal with context.
+  WP2_CHECK_STATUS(context_.Init(use_aom_coeffs));
 
-  WP2_CHECK_STATUS(
-      residual_writer_.Init(use_aom_coeffs_, gparams_->maybe_use_lossy_alpha_));
+  WP2_CHECK_STATUS(residual_writer_.Init(context_.use_aom(),
+                                         gparams_->maybe_use_lossy_alpha_));
 
   const uint32_t num_segments =
       std::min((uint32_t)gparams_->segments_.size(),
@@ -60,9 +61,6 @@
   // Deal with segment ids.
   WP2_CHECK_STATUS(segment_ids_.InitWrite(
       num_segments, gparams_->explicit_segment_ids_, tile_rect.width));
-  // Deal with context.
-  WP2_CHECK_STATUS(
-      context_.Init(use_aom_coeffs_, tile_rect.width, tile_rect.height));
 
   if (gparams_->has_alpha_) {
     WP2_CHECK_STATUS(alpha_writer_.Init(*config_, gparams, context_, yuv,
@@ -97,7 +95,6 @@
   tile_rect_ = other.tile_rect_;
   chroma_subsampling_ = other.chroma_subsampling_;
   gparams_ = other.gparams_;
-  use_aom_coeffs_ = other.use_aom_coeffs_;
 
   WP2_CHECK_STATUS(segment_ids_.CopyFrom(other.segment_ids_));
   WP2_CHECK_STATUS(
@@ -115,7 +112,7 @@
   WP2_CHECK_STATUS(symbols_info.InitLossy(
       gparams_->segments_.size(), gparams_->partition_set_,
       gparams_->maybe_use_lossy_alpha_, GetQualityHint(config_->quality),
-      use_aom_coeffs_));
+      context_.use_aom()));
   symbols_info.SetMinMax(kSymbolModeY, /*min=*/0,
                          /*max=*/gparams_->y_preds_.GetMaxMode());
   if (gparams_->maybe_use_lossy_alpha_) {
@@ -133,7 +130,7 @@
   }
   symbols_info.SetClusters(kSymbolDC, symbols_info.NumClusters(kSymbolDC));
 
-  WP2_CHECK_STATUS(symbol_writer_.Init(symbols_info));
+  WP2_CHECK_STATUS(symbol_writer_.Init(symbols_info, config_->effort));
   WP2_CHECK_STATUS(symbol_writer_.Allocate());
   if (pass_number_ == 0) {
     WP2_CHECK_STATUS(symbol_recorder_.Allocate(
@@ -160,7 +157,7 @@
 
   // TODO(skal): differential-write of gparams_
 
-  enc->PutBool(use_aom_coeffs_, "use_aom_coeffs");
+  enc->PutBool(context_.use_aom(), "use_aom_coeffs");
 
   // Write dictionaries.
   PutLargeRange(num_blocks_, min_num_blocks, max_num_blocks, enc, "num_blocks");
@@ -246,7 +243,6 @@
 }
 
 void SyntaxWriter::WriteBlockBeforeCoeffs(const CodedBlock& cb,
-                                          bool update_ymodes,
                                           SymbolManager* const sm,
                                           ANSEncBase* const enc) {
   segment_ids_.WriteId(cb, sm, enc);
@@ -259,12 +255,7 @@
 
   for (Channel channel : {kYChannel, kAChannel, kUChannel, kVChannel}) {
     if (channel == kAChannel && !cb.HasLossyAlpha()) continue;
-    if (channel == kYChannel || channel == kAChannel) {
-      WriteYAPredictors(cb, channel, &context_.ymodes(), update_ymodes, sm,
-                        enc);
-    } else {
-      WriteUVPredictors(cb, channel, sm, enc);
-    }
+    WritePredictors(cb, channel, sm, enc);
   }
 
   if (chroma_subsampling_ == ChromaSubsampling::kSingleBlock ||
@@ -279,7 +270,7 @@
     WriteTransform(cb, channel, sm, enc);
   }
 
-  if (!use_aom_coeffs_) {
+  if (!context_.use_aom()) {
     ANSDebugPrefix prefix(enc, "coeff_method");
     gparams_->segments_[cb.id_].StoreEncodingMethods(cb, sm, enc);
   }
@@ -292,32 +283,19 @@
   }
 }
 
-void SyntaxWriter::WriteYAPredictors(
-    const CodedBlock& cb, Channel channel,
-    const YModePredictor* const ymode_predictor, bool update_ymodes,
-    SymbolManager* const sm, ANSEncBase* const enc) {
+void SyntaxWriter::WritePredictors(const CodedBlock& cb, Channel channel,
+                                   SymbolManager* const sm,
+                                   ANSEncBase* const enc) {
   ANSDebugPrefix prefix(enc, "pred_modes");
   const CodedBlock::CodingParams& params = cb.GetCodingParams(channel);
-  ANSDebugPrefix prefix2(enc, (channel == kYChannel) ? "y" : "a");
-  if (channel == kYChannel && cb.y_context_is_constant_) return;
   if (channel == kYChannel) {
-    assert(ymode_predictor != nullptr);
-    const_cast<YModePredictor*>(ymode_predictor)
-        ->Write(cb, *params.pred, update_ymodes, enc, sm);
-  } else {
-    assert(channel == kAChannel);
-    sm->Process(kSymbolModeA, params.pred->mode(), "pred", enc);
-  }
-  params.pred->WriteParams(cb, channel, sm, enc);
-}
-
-void SyntaxWriter::WriteUVPredictors(const CodedBlock& cb, Channel channel,
-                                     SymbolManager* const sm,
-                                     ANSEncBase* const enc) {
-  ANSDebugPrefix prefix(enc, "pred_modes");
-  const CodedBlock::CodingParams& params = cb.GetCodingParams(channel);
-  if (channel == kUChannel) {
+    if (cb.y_context_is_constant_) return;
+    sm->Process(kSymbolModeY, params.pred->mode(), "y", enc);
+  } else if (channel == kUChannel) {
     sm->Process(kSymbolModeUV, params.pred->mode(), "uv", enc);
+    // kVChannel is not written as it is the same as kUChannel.
+  } else if (channel == kAChannel) {
+    sm->Process(kSymbolModeA, params.pred->mode(), "a", enc);
   }
   params.pred->WriteParams(cb, channel, sm, enc);
 }
@@ -373,19 +351,21 @@
 
 void SyntaxWriter::RecordBlockHeader(const CodedBlock& cb) {
   ANSEncNoop enc;
-  WriteBlockBeforeCoeffs(cb, /*update_ymodes=*/true, &symbol_recorder_, &enc);
+  WriteBlockBeforeCoeffs(cb, &symbol_recorder_, &enc);
 }
 
 WP2Status SyntaxWriter::WriteBlocks(const Vector<CodedBlock>& cblocks,
+                                    const Vector_u16& size_order_indices,
                                     FrontMgrNxNBase* const mgr,
                                     ANSEnc* const enc) {
   assert(cblocks.size() == num_blocks_);
+  assert(size_order_indices.size() == num_blocks_);
   // Note: we could call segment_ids_.InitMap(bw, bh) again here.
   mgr->Clear();
-  WP2_CHECK_STATUS(
-      residual_writer_.Init(use_aom_coeffs_, gparams_->maybe_use_lossy_alpha_));
+  WP2_CHECK_STATUS(residual_writer_.Init(context_.use_aom(),
+                                         gparams_->maybe_use_lossy_alpha_));
   context_.Reset();
-  for (uint16_t ind : mgr->SizeIndices()) {
+  for (uint16_t ind : size_order_indices) {
     {
       ANSDebugPrefix prefix(enc, "BlockHeader");
       const CodedBlock& cb = cblocks[ind];
@@ -411,7 +391,7 @@
   for (auto c : {kYChannel, kUChannel, kVChannel, kAChannel}) {
     if (c == kAChannel && !cb->HasLossyAlpha()) continue;
     for (uint32_t tf_i = 0; tf_i < cb->GetNumTransforms(c); ++tf_i) {
-      if (use_aom_coeffs_) {
+      if (context_.use_aom()) {
         ResidualWriter::SetGeometry(cb->num_coeffs_[c][tf_i],
                                     &cb->method_[c][tf_i]);
       } else {
@@ -499,7 +479,7 @@
                                    ANSEnc* const enc) {
   {
     ANSDebugPrefix prefix(enc, "BlockHeader");
-    WriteBlockBeforeCoeffs(cb, /*update_ymodes=*/true, &symbol_writer_, enc);
+    WriteBlockBeforeCoeffs(cb, &symbol_writer_, enc);
   }
 
   float previous_cost;
diff --git a/src/enc/wp2_enc_i.h b/src/enc/wp2_enc_i.h
index e6deab6..ab8297d 100644
--- a/src/enc/wp2_enc_i.h
+++ b/src/enc/wp2_enc_i.h
@@ -322,15 +322,11 @@
   // Writes the headers (frozen dictionaries, etc.).
   WP2Status WriteHeader(ANSEncBase* const enc);
   // These write the block header (segment id, preds, etc.).
-  void WriteBlockBeforeCoeffs(const CodedBlock& cb, bool update_ymodes,
-                              SymbolManager* const sm, ANSEncBase* const enc);
+  void WriteBlockBeforeCoeffs(const CodedBlock& cb, SymbolManager* const sm,
+                              ANSEncBase* const enc);
   // Writes the predictors used by the given block for the given channel.
-  static void WriteYAPredictors(const CodedBlock& cb, Channel channel,
-                                const YModePredictor* const ymode_predictor,
-                                bool update_ymodes, SymbolManager* const sm,
-                                ANSEncBase* const enc);
-  static void WriteUVPredictors(const CodedBlock& cb, Channel channel,
-                                SymbolManager* const sm, ANSEncBase* const enc);
+  static void WritePredictors(const CodedBlock& cb, Channel channel,
+                              SymbolManager* const sm, ANSEncBase* const enc);
   // Writes whether the block is split into smaller square transforms.
   static void WriteSplitTransform(const CodedBlock& cb, Channel channel,
                                   SymbolManager* const sm,
@@ -342,7 +338,11 @@
   static void WriteTransform(const CodedBlock& cb, Channel channel,
                              SymbolManager* const sm, ANSEncBase* const enc);
 
+  // Writes all blocks to 'enc'. 'size_order_indices' should be the same size
+  // as 'cblocks', and contain the indices of a permutation of 'cblocks' that
+  // puts it in size order (order in which block sizes are written).
   WP2Status WriteBlocks(const Vector<CodedBlock>& cblocks,
+                        const Vector_u16& size_order_indices,
                         FrontMgrNxNBase* const mgr, ANSEnc* const enc);
 
   SymbolWriter* symbol_writer() { return &symbol_writer_; }
@@ -388,7 +388,6 @@
   ChromaSubsampling chroma_subsampling_;
 
   const GlobalParams* gparams_;
-  bool use_aom_coeffs_;
 
   SegmentIdPredictor segment_ids_;
   BlockContext context_;
diff --git a/src/utils/front_mgr.cc b/src/utils/front_mgr.cc
index 82d786e..b41182e 100644
--- a/src/utils/front_mgr.cc
+++ b/src/utils/front_mgr.cc
@@ -197,8 +197,6 @@
   snapped_ = other.snapped_;
   WP2_CHECK_STATUS(occupancy_size_.CopyInternal(other.occupancy_size_));
   WP2_CHECK_ALLOC_OK(size_stack_.copy_from(other.size_stack_));
-  WP2_CHECK_ALLOC_OK(blocks_.copy_from(other.blocks_));
-  WP2_CHECK_ALLOC_OK(size_indices_.copy_from(other.size_indices_));
   next_ = other.next_;
   return WP2_STATUS_OK;
 }
@@ -216,7 +214,6 @@
       x, y, snapped_ ? GetSnappedBlockSize(x, y, w, h) : GetBlockSize(w, h));
 }
 
-const Vector_u16& FrontMgrNxNBase::SizeIndices() const { return size_indices_; }
 
 //------------------------------------------------------------------------------
 
@@ -262,39 +259,31 @@
          block.w() <= next_.w() && block.h() <= next_.h());
 }
 
-WP2Status FrontMgrLexico::Sort() {
-  std::sort(blocks_.begin(), blocks_.end());
+WP2Status FrontMgrLexico::Sort(VectorNoCtor<Block>& blocks,
+                               Vector_u16& size_order_indices) const {
+  std::sort(blocks.begin(), blocks.end());
   // Block sizes are written to the stream in the same order as the blocks.
-  WP2_CHECK_ALLOC_OK(size_indices_.resize(blocks_.size()));
-  std::iota(size_indices_.begin(), size_indices_.end(), 0);
+  WP2_CHECK_ALLOC_OK(size_order_indices.resize(blocks.size()));
+  std::iota(size_order_indices.begin(), size_order_indices.end(), 0);
 
   // Only CheckBlockList() in debug but don't assert WP2_STATUS_OUT_OF_MEMORY.
   WP2Status status = WP2_STATUS_OK;
-  assert((status = CheckBlockList(), true));  // NOLINT (side effect assert)
+  assert((status = CheckBlockList(blocks), true));  // NOLINT side effect assert
   WP2_CHECK_STATUS(status);
   return WP2_STATUS_OK;
 }
 
-WP2Status FrontMgrLexico::CheckBlockList() const {
+WP2Status FrontMgrLexico::CheckBlockList(
+    const VectorNoCtor<Block>& blocks) const {
   FrontMgrLexico mgr;
   const uint32_t width = bwidth_ * kMinBlockSizePix;
   const uint32_t height = bheight_ * kMinBlockSizePix;
   WP2_CHECK_STATUS(mgr.Init(partition_set_, snapped_, width, height));
-  for (uint32_t i = 0; i < blocks_.size(); ++i) {
-    const Block& v = blocks_[i];
+  for (uint32_t i = 0; i < blocks.size(); ++i) {
+    const Block& v = blocks[i];
     const Block b(mgr.next_.x(), mgr.next_.y(), v.dim());
 
     // Compare to the input block.
-    const uint32_t bw = BlockWidth[v.dim()];
-    const uint32_t bh = BlockHeight[v.dim()];
-    if (!(bw <= b.w() && bh <= b.h()) ||
-        !(bw > 0 && bw <= kMaxBlockSize && bh > 0 && bh <= kMaxBlockSize)) {
-      fprintf(stderr,
-              "Block validation error: size is too large! "
-              "@%d,%d: v=%dx%d (w/h=%d,%d)   b=%dx%d\n",
-              v.x(), v.y(), bw, bh, width, height, b.w(), b.h());
-      assert(false);
-    }
     if (!(b.x() == v.x() && b.y() == v.y())) {
       fprintf(stderr, "Block validation error: wrong x,y position! "
                       "@%d,%d: expected %d,%d\n", v.x(), v.y(), b.x(), b.y());
@@ -394,21 +383,28 @@
 // Though this algorithm is O(N^2) in the number of blocks, it is actually O(N)
 // in practice as the initial blocks are sorted and when searching for
 // neighboring blocks, they are not far in that list.
-WP2Status FrontMgrMax::Sort() {
-  Clear();
+WP2Status FrontMgrMax::Sort(VectorNoCtor<Block>& blocks,
+                            Vector_u16& size_order_indices) const {
+  // Sort() is const, we don't want to modify this instance's state, so we
+  // use a new instance for sorting purposes.
+  FrontMgrMax mgr_tmp;
+  WP2_CHECK_STATUS(mgr_tmp.Init(partition_set_, snapped_,
+                                bwidth_ * kMinBlockSizePix,
+                                bheight_ * kMinBlockSizePix));
+
   uint32_t ind = 0;
   // blocks_size will contain the blocks in size order while blocks_ contains
   // the blocks sorted in block order.
   VectorNoCtor<WP2::Block> blocks_size;
-  WP2_CHECK_ALLOC_OK(blocks_size.reserve(blocks_.size()));
-  while (ind < blocks_.size()) {
+  WP2_CHECK_ALLOC_OK(blocks_size.reserve(blocks.size()));
+  while (ind < blocks.size()) {
     uint32_t best_ind = ind;
-    if (size_stack_.empty()) {
+    if (mgr_tmp.size_stack_.empty()) {
       // If we have no queue of elements to check, find the leftmost block at
       // lowest heights.
-      Block best = blocks_[ind];
-      for (uint32_t i = ind + 1; i < blocks_.size(); ++i) {
-        const Block& tmp = blocks_[i];
+      Block best = blocks[ind];
+      for (uint32_t i = ind + 1; i < blocks.size(); ++i) {
+        const Block& tmp = blocks[i];
         if (tmp.y() < best.y() || (tmp.y() == best.y() && tmp.x() < best.x())) {
           best = tmp;
           best_ind = i;
@@ -417,52 +413,52 @@
     } else {
       // If the left context is not full, find the first block that can fill
       // it.
-      const uint32_t x = size_stack_.back().block.x() - 1;
-      const uint32_t y = occupancy_size_.GetOccupancy(x);
-      best_ind = blocks_.size();
-      for (uint32_t i = ind; i < blocks_.size(); ++i) {
-        const Block& b = blocks_[i];
+      const uint32_t x = mgr_tmp.size_stack_.back().block.x() - 1;
+      const uint32_t y = mgr_tmp.occupancy_size_.GetOccupancy(x);
+      best_ind = blocks.size();
+      for (uint32_t i = ind; i < blocks.size(); ++i) {
+        const Block& b = blocks[i];
         if (b.x() <= x && x < b.x() + b.w() && b.y() <= y &&
             y < b.y() + b.h()) {
           best_ind = i;
           break;
         }
       }
-      assert(best_ind < blocks_.size());
+      assert(best_ind < blocks.size());
     }
     // Add that new block to the list of blocks.
-    const Block& block = blocks_[best_ind];
+    const Block& block = blocks[best_ind];
     WP2_CHECK_ALLOC_OK(blocks_size.push_back(block));
     Block tmp;
-    WP2_CHECK_ALLOC_OK(UseSize(block.dim(), best_ind, &tmp));
+    WP2_CHECK_ALLOC_OK(mgr_tmp.UseSize(block.dim(), best_ind, &tmp));
     assert(block == tmp);
 
     // Empty the queue if needed.
     while (true) {
       Info info;
-      if (!UseFinal(&info)) break;
+      if (!mgr_tmp.UseFinal(&info)) break;
       const Block blk = info.block;
       // Place the block at its right spot.
-      for (uint32_t i = ind; i < blocks_.size(); ++i) {
-        if (blocks_[i] == blk) {
-          std::swap(blocks_[i], blocks_[ind]);
+      for (uint32_t i = ind; i < blocks.size(); ++i) {
+        if (blocks[i] == blk) {
+          std::swap(blocks[i], blocks[ind]);
           ++ind;
           break;
         }
       }
     }
-    UpdateNextBlock();
-    if (ind == blocks_.size()) break;
+    mgr_tmp.UpdateNextBlock();
+    if (ind == blocks.size()) break;
   }
 
   // Convert blocks_size to indices.
-  WP2_CHECK_ALLOC_OK(size_indices_.resize(blocks_.size()));
-  std::iota(size_indices_.begin(), size_indices_.end(), 0);
+  WP2_CHECK_ALLOC_OK(size_order_indices.resize(blocks.size()));
+  std::iota(size_order_indices.begin(), size_order_indices.end(), 0);
   ind = 0;
   for (const Block& b : blocks_size) {
-    for (uint32_t i = ind; i < blocks_.size(); ++i) {
-      if (blocks_[size_indices_[i]] == b) {
-        std::swap(size_indices_[i], size_indices_[ind]);
+    for (uint32_t i = ind; i < blocks.size(); ++i) {
+      if (blocks[size_order_indices[i]] == b) {
+        std::swap(size_order_indices[i], size_order_indices[ind]);
         ++ind;
         break;
       }
@@ -471,17 +467,17 @@
 
   // Only CheckBlockList() in debug but don't assert WP2_STATUS_OUT_OF_MEMORY.
   WP2Status status = WP2_STATUS_OK;
-  assert((status = CheckBlockList(), true));  // NOLINT (side effect assert)
+  assert((status = CheckBlockList(blocks), true));  // NOLINT side effect assert
   WP2_CHECK_STATUS(status);
 
   return WP2_STATUS_OK;
 }
 
-WP2Status FrontMgrMax::CheckBlockList() const {
+WP2Status FrontMgrMax::CheckBlockList(const VectorNoCtor<Block>& blocks) const {
   FrontMgrBase occupancy;
   WP2_CHECK_STATUS(occupancy.InitBase(bwidth_ * kMinBlockSizePix,
                                       bheight_ * kMinBlockSizePix));
-  for (const Block& b : blocks_) {
+  for (const Block& b : blocks) {
     // Check we have context above.
     if (b.y() > 0) {
       for (uint32_t x = b.x(); x < b.x() + b.w(); ++x) {
@@ -492,7 +488,7 @@
       }
     }
     // Check we have context on the left.
-    if (!IsFinal(b)) {
+    if (!DoIsFinal(occupancy, b)) {
       fprintf(stderr, "Context not good at %d %d ", b.x(), b.y());
       assert(false);
     }
@@ -502,7 +498,13 @@
 }
 
 bool FrontMgrMax::IsFinal(const WP2::Block& block) const {
-  return (block.x() == 0 || occupancy_[block.x() - 1] >= block.y() + block.h());
+  return DoIsFinal(*this, block);
+}
+
+bool FrontMgrMax::DoIsFinal(const FrontMgrBase& occupancy,
+                            const WP2::Block& block) const {
+  return (block.x() == 0 ||
+          occupancy.GetOccupancy(block.x() - 1) >= block.y() + block.h());
 }
 
 bool FrontMgrMax::IsFinalSize(const WP2::Block& block) const {
@@ -555,12 +557,13 @@
   return true;
 }
 
-WP2Status FrontMgrArea::Sort() {
-  std::sort(blocks_.begin(), blocks_.end(),
+WP2Status FrontMgrArea::Sort(VectorNoCtor<Block>& blocks,
+                             Vector_u16& size_order_indices) const {
+  std::sort(blocks.begin(), blocks.end(),
             Comp(kMaxTileSize / kMinBlockSizePix, area_width_, area_height_));
   // Same block and size order.
-  WP2_CHECK_ALLOC_OK(size_indices_.resize(blocks_.size()));
-  std::iota(size_indices_.begin(), size_indices_.end(), 0);
+  WP2_CHECK_ALLOC_OK(size_order_indices.resize(blocks.size()));
+  std::iota(size_order_indices.begin(), size_order_indices.end(), 0);
   return WP2_STATUS_OK;
 }
 
diff --git a/src/utils/front_mgr.h b/src/utils/front_mgr.h
index c828747..e6dd617 100644
--- a/src/utils/front_mgr.h
+++ b/src/utils/front_mgr.h
@@ -128,17 +128,11 @@
 
   void Clear() override;
 
-  // Returns all the underlying blocks of the tile, in arbitrary order(?)
-  // or in block order if Sort() has been called.
-  const VectorNoCtor<Block>& Blocks() const { return blocks_; }
-  VectorNoCtor<Block>& Blocks() { return blocks_; }
-
-  // Returns the indices of the blocks in Blocks() (that are sorted in block
-  // order) so that they are in size order.
-  const Vector_u16& SizeIndices() const;
-
-  // Sorts the Blocks() in block order.
-  virtual WP2Status Sort() = 0;
+  // Sorts the given blocks in block order and fills 'size_order_indices' with
+  // the indices of a permutation of 'blocks' that puts it in size order (order
+  // in which block sizes are written).
+  virtual WP2Status Sort(VectorNoCtor<Block>& blocks,
+                         Vector_u16& size_order_indices) const = 0;
 
   // Block-order related functions.
 
@@ -185,8 +179,6 @@
   // rest of the block is written.
   VectorNoCtor<Info> size_stack_;
   FrontMgrBase occupancy_size_;
-  VectorNoCtor<Block> blocks_;
-  Vector_u16 size_indices_;
   // Next max possible block (for reading/writing block sizes).
   Block next_;
 };
@@ -207,12 +199,13 @@
   WP2_NO_DISCARD
   bool UseSize(BlockSize dim, uint32_t ind, Block* const block) override;
   virtual void UndoUseSize(const Block& block);
-  WP2Status Sort() override;
+  WP2Status Sort(VectorNoCtor<Block>& blocks,
+                 Vector_u16& size_order_indices) const override;
   bool IsFinal(const WP2::Block& block) const override;
 
  private:
   // Returns whether the 'Blocks()' are in the order they will be written.
-  WP2Status CheckBlockList() const;
+  WP2Status CheckBlockList(const VectorNoCtor<Block>& blocks) const;
 };
 
 //------------------------------------------------------------------------------
@@ -227,16 +220,18 @@
   bool TryGetNextBlock(BlockSize size, Block* const block) const override;
   WP2_NO_DISCARD
   bool UseSize(BlockSize dim, uint32_t ind, Block* const block) override;
-  WP2Status Sort() override;
+  WP2Status Sort(VectorNoCtor<Block>& blocks,
+                 Vector_u16& size_order_indices) const override;
   bool IsFinal(const WP2::Block& block) const override;
 
  private:
+  bool DoIsFinal(const FrontMgrBase& occupancy, const WP2::Block& block) const;
   // Returns true if a block is considered final in the size occupancy.
   bool IsFinalSize(const WP2::Block& block) const;
   // Updates the 'next_' block.
   void UpdateNextBlock();
   // Returns whether the 'Blocks()' are in the order they will be written.
-  WP2Status CheckBlockList() const;
+  WP2Status CheckBlockList(const VectorNoCtor<Block>& blocks) const;
 };
 
 // This is the FrontMgr class that should be used by default.
@@ -283,7 +278,8 @@
   bool TryGetNextBlock(BlockSize size, Block* const block) const override;
   WP2_NO_DISCARD
   bool UseSize(BlockSize dim, uint32_t ind, Block* const block) override;
-  WP2Status Sort() override;
+  WP2Status Sort(VectorNoCtor<Block>& blocks,
+                 Vector_u16& size_order_indices) const override;
   bool IsFinal(const WP2::Block& block) const override;
 
  protected:
diff --git a/src/utils/quantizer.cc b/src/utils/quantizer.cc
index 01f76cb..ab4d53b 100644
--- a/src/utils/quantizer.cc
+++ b/src/utils/quantizer.cc
@@ -32,14 +32,31 @@
 WP2Status Quantizer::Allocate(uint32_t range_max) {
   // We need at least kMaxFreqBits elements to store Huffman probabilities.
   histogram_size_max_ = std::max(kMaxFreqBits + 1u, range_max);
-  const size_t histogram_size = 4 * sizeof(uint32_t) + sizeof(uint16_t);
-  WP2_CHECK_STATUS(buffer_.Resize(
-      kConfigNbr * histogram_size_max_ * histogram_size, /*keep_bytes=*/false));
+  WP2_CHECK_STATUS(
+      buffer32_.Resize(kConfigNbr * histogram_size_max_ * 4 * sizeof(uint32_t),
+                       /*keep_bytes=*/false));
+  WP2_CHECK_STATUS(
+      buffer16_.Resize(kConfigNbr * histogram_size_max_ * sizeof(uint16_t),
+                       /*keep_bytes=*/false));
+  uint32_t* buffer32 = (uint32_t*)buffer32_.bytes;
+  uint16_t* buffer16 = (uint16_t*)buffer16_.bytes;
+  for (Config& c : configs_) {
+    c.histogram_quantized = buffer32;
+    buffer32 += histogram_size_max_;
+    c.histogram_to_write = buffer32;
+    buffer32 += histogram_size_max_;
+    c.tmp_histogram_to_write = buffer32;
+    buffer32 += histogram_size_max_;
+    c.histo.counts = buffer32;
+    buffer32 += histogram_size_max_;
+    c.histo.mapping = buffer16;
+    buffer16 += histogram_size_max_;
+  }
 
-  for (size_t i = 0; i < kConfigNbr; ++i) {
+  for (auto& c : configs_) {
     // Make sure the recursion buffers are properly allocated.
-    WP2_CHECK_ALLOC_OK(histogram_sub_[i].resize(kMaxFreqBits + 1));
-    WP2_CHECK_ALLOC_OK(mapping_sub_[i].resize(kMaxFreqBits + 1));
+    WP2_CHECK_ALLOC_OK(c.histogram_sub.resize(kMaxFreqBits + 1));
+    WP2_CHECK_ALLOC_OK(c.mapping_sub.resize(kMaxFreqBits + 1));
   }
 
   WP2_CHECK_ALLOC_OK(stats_buffer_.resize(histogram_size_max_));
@@ -52,25 +69,11 @@
                          Config** const config_best) {
   assert(effort >= 0 && effort <= 9);
   *config_best = &configs_[0];
-  configs_used_[0] = true;
-  for (size_t i = 1; i < kConfigNbr; ++i) configs_used_[i] = false;
-
-  uint32_t* buffer = (uint32_t*)buffer_.bytes;
-  for (size_t i = 0; i < kConfigNbr; ++i) {
-    // Make sure the recursion buffers are properly allocated.
-    histogram_quantized_[i] = buffer;
-    buffer += histogram_size_max_;
-    histogram_to_write_[i] = buffer;
-    buffer += histogram_size_max_;
-    configs_[i].histogram_to_write = buffer;
-    buffer += histogram_size_max_;
-    configs_[i].histo.counts = buffer;
-    buffer += histogram_size_max_;
-    configs_[i].histo.mapping = (uint16_t*)buffer;
-    buffer += histogram_size_max_ * sizeof(uint16_t) / sizeof(uint32_t);
-  }
+  for (uint32_t i = 0; i < kConfigNbr; ++i) configs_->used = false;
+  (*config_best)->used = true;
   QuantizeImpl(histogram, mapping, size_sparse, symbol_range,
-               1 + WP2Log2Floor(max_count), effort, 0);
+               1 + WP2Log2Floor(max_count), effort,
+               /*cost_max=*/std::numeric_limits<float>::max(), *config_best);
 }
 
 // The quantization implementation uses the Config cache of the class during
@@ -78,29 +81,27 @@
 void Quantizer::QuantizeImpl(const uint32_t* const histogram,
                              const uint16_t* const mapping, size_t size_sparse,
                              uint32_t symbol_range, uint32_t n_pixels_bits,
-                             int effort, size_t config_index, float cost_max) {
-  assert(config_index < kConfigNbr);
-  Config* const config_best = &configs_[config_index];
+                             int effort, float cost_max, Config* const config) {
+  assert(config != nullptr);
   if (size_sparse == 0) {
-    config_best->param.type = Raw;
-    config_best->param.is_sparse = false;
-    config_best->param.max_freq_bits = 0;
-    config_best->histogram_to_write = nullptr;
-    config_best->size_to_write = 0;
-    config_best->histo.counts = nullptr;
-    config_best->histo.mapping = nullptr;
-    config_best->histo.nnz = 0;
-    config_best->cost = 0;
+    config->param.type = Raw;
+    config->param.is_sparse = false;
+    config->param.max_freq_bits = 0;
+    config->histogram_to_write = nullptr;
+    config->size_to_write = 0;
+    config->histo.counts = nullptr;
+    config->histo.mapping = nullptr;
+    config->histo.nnz = 0;
+    config->cost = 0;
     return;
   }
 
   // We simplify the mapping to be stored as the difference between
   // consecutive terms.
-  config_best->histo.nnz = size_sparse;
-  std::copy(mapping, mapping + size_sparse, config_best->histo.mapping);
+  config->histo.nnz = size_sparse;
+  std::copy(mapping, mapping + size_sparse, config->histo.mapping);
 
   // Pre-compute some quantization configurations for clarity.
-  uint8_t* const bits = bits_[config_index];
   size_t n_max_freq_bits = 0;
   {
     uint32_t max_freq_bits_max =
@@ -108,23 +109,14 @@
     max_freq_bits_max = std::min(max_freq_bits_max, kMaxFreqBits);
     const uint32_t max_freq_bits_min =
         (1 * effort + max_freq_bits_max * (9 - effort)) / 9;
-    for (uint32_t max_freq_bits = max_freq_bits_max;
-         max_freq_bits >= max_freq_bits_min; --max_freq_bits) {
-      bits[n_max_freq_bits++] = max_freq_bits;
-    }
-    // Start by doing some strong quantization to prune out the ones that would
-    // barely change the bits cost.
-    std::swap(bits[0], bits[(n_max_freq_bits - 1) / 2]);
-    if (n_max_freq_bits > 1) {
-      std::swap(bits[1], bits[(n_max_freq_bits - 1) / 4]);
-    }
-    if (n_max_freq_bits > 2) {
-      std::swap(bits[2], bits[(n_max_freq_bits - 1) * 3 / 4]);
+    for (uint32_t max_freq_bits = max_freq_bits_min;
+         max_freq_bits <= max_freq_bits_max; ++max_freq_bits) {
+      config->bits[n_max_freq_bits++] = max_freq_bits;
     }
   }
 
-  uint32_t* histogram_quantized = histogram_quantized_[config_index];
-  uint32_t* histogram_to_write = histogram_to_write_[config_index];
+  uint32_t* histogram_quantized = config->histogram_quantized;
+  uint32_t* histogram_to_write = config->tmp_histogram_to_write;
 
   // Compute some constant costs.
   float cost_probability_size, cost_mapping;
@@ -138,7 +130,7 @@
     cost_probability_size = WP2Log2(symbol_range + 1 - size_sparse);
     // Small speed improvements: if we use the whole range, no need to even
     // consider a potential mapping.
-    cost_mapping = StoreMapping(config_best->histo.mapping, size_sparse,
+    cost_mapping = StoreMapping(config->histo.mapping, size_sparse,
                                 symbol_range, stats_buffer_.data(), nullptr);
   }
   // Cost for the sparse bit and the histogram type.
@@ -146,15 +138,15 @@
   n_pixels_bits = std::min(n_pixels_bits, kMaxFreqBits);
 
   // Go over the quantification configurations and pick the best one.
-  config_best->cost = cost_max;
+  config->cost = cost_max;
   for (size_t i = 0; i < n_max_freq_bits; ++i) {
     // No need to continue if any pre-computed cost we have is already bigger.
-    if (cost_sparse_bit_and_type + cost_mapping > config_best->cost &&
-        cost_sparse_bit_and_type + cost_probability_size > config_best->cost) {
+    if (cost_sparse_bit_and_type + cost_mapping > config->cost &&
+        cost_sparse_bit_and_type + cost_probability_size > config->cost) {
       break;
     }
     for (const ConfigType type : {Raw, Huffman}) {
-      uint8_t max_freq_bits = bits[i];
+      uint8_t max_freq_bits = config->bits[i];
       // Compute the cost of storing the data with the quantized probabilities.
       float cost_ini;
 
@@ -175,14 +167,14 @@
 
       for (const bool is_sparse : {false, true}) {
         // Reset variables.
-        max_freq_bits = bits[i];
+        max_freq_bits = config->bits[i];
         float cost = cost_ini;
         // Add the cost for the mapping or the probability size.
         cost += is_sparse ? cost_mapping : cost_probability_size;
 
         // No need to continue if the bit cost without the histogram cost is
         // already bigger.
-        if (cost >= config_best->cost) continue;
+        if (cost >= config->cost) continue;
 
         // Prepare the vector in which we will write the histogram.
         size_t histogram_to_write_size;
@@ -248,29 +240,29 @@
         cost += cost_histo;
 
         // Keep the best configuration.
-        if (cost >= config_best->cost) {
+        if (cost >= config->cost) {
           continue;
         }
-        config_best->param.type = type;
-        config_best->param.is_sparse = is_sparse;
-        config_best->param.max_freq_bits = max_freq_bits;
-        config_best->size_to_write = histogram_to_write_size;
-        std::swap(config_best->histogram_to_write, histogram_to_write);
+        config->param.type = type;
+        config->param.is_sparse = is_sparse;
+        config->param.max_freq_bits = max_freq_bits;
+        config->size_to_write = histogram_to_write_size;
+        std::swap(config->histogram_to_write, histogram_to_write);
         if (is_sparse) {
-          std::swap(config_best->histo.counts, histogram_quantized);
+          std::swap(config->histo.counts, histogram_quantized);
         } else {
           std::copy(histogram_quantized,
                     histogram_quantized + histogram_to_write_size,
-                    config_best->histo.counts);
+                    config->histo.counts);
         }
-        config_best->cost = cost;
+        config->cost = cost;
       }
     }
   }
 
   // Reset the common temporary variables.
-  histogram_quantized_[config_index] = histogram_quantized;
-  histogram_to_write_[config_index] = histogram_to_write;
+  config->histogram_quantized = histogram_quantized;
+  config->tmp_histogram_to_write = histogram_to_write;
 }
 
 }  // namespace WP2
diff --git a/src/utils/quantizer.h b/src/utils/quantizer.h
index c576842..d25842b 100644
--- a/src/utils/quantizer.h
+++ b/src/utils/quantizer.h
@@ -63,11 +63,22 @@
     size_t size_to_write;
     HistogramSparse histo;
     float cost;
-  };
 
-  Quantizer() {
-    for (auto& c : configs_used_) c = false;
-  }
+    // Filled by QuantizeImpl():
+    void Reset() { used = false; }
+
+   private:
+    bool used = false;
+    // For each recursion level, each set of param can be Raw/Huffman, sparse or
+    // not and have several levels of recursion.
+    uint8_t bits[kMaxFreqBits];
+    Vector_u32 histogram_sub;
+    Vector_u16 mapping_sub;
+
+    uint32_t* histogram_quantized;
+    uint32_t* tmp_histogram_to_write;
+    friend Quantizer;
+  };
 
   // range_max is the maximum range of the used symbols (maximum value + 1).
   WP2Status Allocate(uint32_t range_max);
@@ -83,35 +94,21 @@
                 int effort, Config** const config_best);
 
  private:
-  // Free the config from the buffer to make it re-usable.
-  void ResetConfig(const Config* c) {
-    const size_t c_ind = c - &configs_[0];
-    configs_used_[c_ind] = false;
-  }
-
   // Implementation of the quantization that can call itself to compress its own
   // coefficients.
   // 'cost_max' is the value above which we can early exit.
   void QuantizeImpl(const uint32_t* const histogram,
                     const uint16_t* const mapping, size_t size_sparse,
                     uint32_t symbol_range, uint32_t n_pixels_bits, int effort,
-                    size_t config_index,
-                    float cost_max = std::numeric_limits<float>::max());
+                    float cost_max, Config* const config);
 
   // TODO(vrabaud) Investigate to see if it is worth having more levels.
   static constexpr size_t kConfigNbr = 2;
   Config configs_[kConfigNbr];
-  bool configs_used_[kConfigNbr];
-  // For each recursion level, each set of param can be Raw/Huffman, sparse or
-  // not and have several levels of recursion.
-  uint8_t bits_[kConfigNbr][kMaxFreqBits];
-  Vector_u32 histogram_sub_[kConfigNbr];
-  Vector_u16 mapping_sub_[kConfigNbr];
-
   uint32_t histogram_size_max_;
-  uint32_t* histogram_quantized_[kConfigNbr];
-  uint32_t* histogram_to_write_[kConfigNbr];
-  Data buffer_;  // Big memory chunk storing the buffers above.
+
+  Data buffer32_;  // Big memory chunk storing the uint32_t buffers above.
+  Data buffer16_;  // Big memory chunk storing the uint16_t buffers above.
 
   VectorNoCtor<OptimizeArrayStorageStat> stats_buffer_;
 };
diff --git a/tests/test_ans.cc b/tests/test_ans.cc
index 44e039c..3e20d81 100644
--- a/tests/test_ans.cc
+++ b/tests/test_ans.cc
@@ -625,27 +625,29 @@
   EXPECT_WP2_OK(quantizer.Allocate(kMaxSymbol));
 
   Quantizer::Config* config = nullptr;
-  quantizer.Quantize(histogram.data(), mapping.data(), size_sparse, kMaxSymbol,
-                     /*max_count=*/16, /*effort=*/5, &config);
+  for (int effort : {0, 5, 9}) {
+    quantizer.Quantize(histogram.data(), mapping.data(), size_sparse,
+                       kMaxSymbol, /*max_count=*/16, effort, &config);
 
-  // Display results.
-  if (kVerbose) {
-    printf("Cost: %f\n", config->cost);
-    if (config->param.is_sparse) {
-      printf("Histogram is sparse:\n(index, count - 1):\n");
-      for (uint32_t i = 0; i < config->size_to_write; ++i) {
-        printf("(%d,%d) ", mapping[i], config->histogram_to_write[i]);
+    // Display results.
+    if (kVerbose) {
+      printf("Cost: %f\n", config->cost);
+      if (config->param.is_sparse) {
+        printf("Histogram is sparse:\n(index, count - 1):\n");
+        for (uint32_t i = 0; i < config->size_to_write; ++i) {
+          printf("(%d,%d) ", mapping[i], config->histogram_to_write[i]);
+        }
+        printf("\n");
+      } else {
+        printf("Histogram is not sparse:\n");
+        for (uint32_t i = 0; i < config->size_to_write; ++i) {
+          printf("%d ", config->histogram_to_write[i]);
+        }
+        printf("\n");
       }
-      printf("\n");
-    } else {
-      printf("Histogram is not sparse:\n");
-      for (uint32_t i = 0; i < config->size_to_write; ++i) {
-        printf("%d ", config->histogram_to_write[i]);
+      if (config->param.type == Quantizer::ConfigType::Huffman) {
+        printf("The counts above should be interpreted as exponents of 2.\n");
       }
-      printf("\n");
-    }
-    if (config->param.type == Quantizer::ConfigType::Huffman) {
-      printf("The counts above should be interpreted as exponents of 2.\n");
     }
   }
 }
diff --git a/tests/test_aom_residuals.cc b/tests/test_aom_residuals.cc
index 06fc0a1..bd6f736 100644
--- a/tests/test_aom_residuals.cc
+++ b/tests/test_aom_residuals.cc
@@ -62,7 +62,7 @@
     std::unique_ptr<WP2::SymbolWriter>
       sw(new (WP2Allocable::nothrow) WP2::SymbolWriter);
     ASSERT_TRUE(sw != nullptr);
-    ASSERT_WP2_OK(sw->Init(info));
+    ASSERT_WP2_OK(sw->Init(info, /*effort=*/5));
     ASSERT_WP2_OK(sw->Allocate());
     SymbolRecorder recorder;
     ASSERT_WP2_OK(recorder.Allocate(info, /*num_records=*/num_iters));
diff --git a/tests/test_coded_block.cc b/tests/test_coded_block.cc
index 4dbc1d4..2d6f4f8 100644
--- a/tests/test_coded_block.cc
+++ b/tests/test_coded_block.cc
@@ -112,7 +112,7 @@
   ASSERT_TRUE(transforms.push_back(kDctDct));
 
   BlockContext context;
-  WP2_ASSERT_STATUS(context.Init(/*use_aom=*/false, width, height));
+  WP2_ASSERT_STATUS(context.Init(/*use_aom=*/false));
   ASSERT_WP2_OK(cb.FindBestPredTf(
       EncoderConfig::kDefault, tile_rect, predictors, segment, context,
       kYChannel, kNumChannels, /*reduced=*/false, transforms, &counters));
diff --git a/tests/test_front_mgr.cc b/tests/test_front_mgr.cc
index b39d510..5d21830 100644
--- a/tests/test_front_mgr.cc
+++ b/tests/test_front_mgr.cc
@@ -100,22 +100,23 @@
     FrontMgrMax mgr_max;
     FrontMgrArea mgr_area(kAreaSize, kAreaSize);
 
+    VectorNoCtor<Block> blocks;
+    Vector_u16 size_order_indices;
     FrontMgrNxNBase* const mgr =
         GetMgr(front_mgr_type, &mgr_lex, &mgr_max, &mgr_area);
-    CreateData(w, h, GetSnapped(front_mgr_type), &gen, &mgr->Blocks());
+    CreateData(w, h, GetSnapped(front_mgr_type), &gen, &blocks);
 
     ASSERT_WP2_OK(mgr->Init(ALL_RECTS, GetSnapped(front_mgr_type), w, h));
-    ASSERT_WP2_OK(mgr->Sort());
-    ASSERT_EQ(mgr->Blocks().size(), mgr->SizeIndices().size());
+    ASSERT_WP2_OK(mgr->Sort(blocks, size_order_indices));
+    ASSERT_EQ(blocks.size(), size_order_indices.size());
     // Check all indices are used only once.
-    ASSERT_EQ(
-        mgr->Blocks().size(),
-        std::set<uint16_t>(mgr->SizeIndices().begin(), mgr->SizeIndices().end())
-            .size());
+    ASSERT_EQ(blocks.size(), std::set<uint16_t>(size_order_indices.begin(),
+                                                size_order_indices.end())
+                                 .size());
     // Go over all indices and make sure it works.
     mgr->Clear();
-    for (uint16_t i : mgr->SizeIndices()) {
-      const Block& block = mgr->Blocks()[i];
+    for (uint16_t i : size_order_indices) {
+      const Block& block = blocks[i];
 
       const Block max_block = mgr->GetMaxPossibleBlock();
       ASSERT_GE(block.x(), max_block.x());
@@ -162,14 +163,16 @@
 
     ASSERT_WP2_OK(mgr->Init(ALL_RECTS, GetSnapped(front_mgr_type), w, h));
 
-    CreateData(w, h, GetSnapped(front_mgr_type), &gen, &mgr->Blocks());
-    ASSERT_WP2_OK(mgr->Sort());
+    VectorNoCtor<Block> blocks;
+    Vector_u16 size_order_indices;
+
+    CreateData(w, h, GetSnapped(front_mgr_type), &gen, &blocks);
+    ASSERT_WP2_OK(mgr->Sort(blocks, size_order_indices));
     mgr->Clear();  // Sort() messes up the FrontMgr state so clear it.
 
-    int32_t num_blocks_before_cloning =
-        gen.Get<int32_t>(1, mgr->Blocks().size());
+    int32_t num_blocks_before_cloning = gen.Get<int32_t>(1, blocks.size());
 
-    for (uint16_t i : mgr->SizeIndices()) {
+    for (uint16_t i : size_order_indices) {
       if (num_blocks_before_cloning == 0) {
         ASSERT_WP2_OK((front_mgr_type == kLexico)
                           ? mgr_lex_clone.CopyFrom(mgr_lex)
@@ -178,24 +181,21 @@
                                 : mgr_area_clone.CopyFrom(mgr_area));
       }
 
-      const Block& block = mgr->Blocks()[i];
+      const Block& block = blocks[i];
       Block block_tmp;
       ASSERT_TRUE(mgr->TryGetNextBlock(block.dim(), &block_tmp));
 
       if (num_blocks_before_cloning <= 0) {
         // The clone was copied. Verify that it returns the same values as the
         // original 'map' and apply the same modifications to it from now on.
-        const Block& block_clone = mgr_clone->Blocks()[i];
         Block block_tmp_clone;
-        ASSERT_TRUE(
-            mgr_clone->TryGetNextBlock(block_clone.dim(), &block_tmp_clone));
+        ASSERT_TRUE(mgr_clone->TryGetNextBlock(block.dim(), &block_tmp_clone));
 
-        ASSERT_EQ(block, block_clone);
         ASSERT_EQ(block_tmp, block_tmp_clone);
         ASSERT_EQ(mgr->Done(), mgr_clone->Done());
 
-        ASSERT_TRUE(mgr_clone->UseSize(
-            block_clone.dim(), /*ind=*/0u, &block_tmp_clone));
+        ASSERT_TRUE(
+            mgr_clone->UseSize(block.dim(), /*ind=*/0u, &block_tmp_clone));
         while (mgr_clone->UseFinal()) {
         }
       }
diff --git a/tests/test_quant_mtx.cc b/tests/test_quant_mtx.cc
index de6dafc..51085de 100644
--- a/tests/test_quant_mtx.cc
+++ b/tests/test_quant_mtx.cc
@@ -320,7 +320,7 @@
       sw(new (WP2Allocable::nothrow) WP2::SymbolWriter);
   ASSERT_TRUE(sw != nullptr);
 
-  ASSERT_WP2_OK(sw->Init(symbols_info));
+  ASSERT_WP2_OK(sw->Init(symbols_info, /*effort=*/5));
   ASSERT_WP2_OK(sw->Allocate());
 
   const int16_t yuv_min = -512;
@@ -350,7 +350,6 @@
     TypeParam mgr;
     ASSERT_WP2_OK(
         mgr.Init(ALL_RECTS, /*snapped=*/false, rect.width, rect.height));
-    EXPECT_TRUE(mgr.Blocks().resize(1));
     cb.SetRange(yuv_min, yuv_max);
     cb.SetDim(/*block=*/{/*x=*/0, /*y=*/0, /*dim=*/block_size}, mgr);
     cb.y_context_is_constant_ = false;
@@ -399,6 +398,7 @@
                                       cb.coeffs_[kVChannel][tf_i],
                                       &cb.num_coeffs_[kVChannel][tf_i]);
 
+        mgr.Clear();
         SymbolRecorder recorder;
         ASSERT_WP2_OK(recorder.Allocate(symbols_info, /*num_records=*/0));
         Counters counters;
@@ -426,7 +426,11 @@
         ASSERT_WP2_OK(syntax_writer.Record(cb));
         ASSERT_WP2_OK(syntax_writer.RecordSize(mgr, cb.dim()));
         ASSERT_WP2_OK(syntax_writer.WriteHeader(&enc));
-        ASSERT_WP2_OK(syntax_writer.WriteBlocks(cblocks, &mgr, &enc));
+        Vector_u16 size_order_indices;
+        ASSERT_TRUE(size_order_indices.resize(cblocks.size()));
+        std::iota(size_order_indices.begin(), size_order_indices.end(), 0);
+        ASSERT_WP2_OK(
+            syntax_writer.WriteBlocks(cblocks, size_order_indices, &mgr, &enc));
       }
     }
   }
diff --git a/tests/test_symbols.cc b/tests/test_symbols.cc
index 7cc5622..82249a0 100644
--- a/tests/test_symbols.cc
+++ b/tests/test_symbols.cc
@@ -177,7 +177,7 @@
   void WriteHeader(const SymbolsInfoTest& info, const SymbolRecorder& recorder,
                    uint32_t* max_nnz, ANSDictionaries* const dicts,
                    ANSEnc* const enc, SymbolWriter* const sw) {
-    ASSERT_WP2_OK(sw->Init(info));
+    ASSERT_WP2_OK(sw->Init(info, /*effort=*/5));
     ASSERT_WP2_OK(sw->Allocate());
     // Get the maximum number of non-zero values by aggregating over all
     // clusters.
@@ -644,7 +644,7 @@
   std::unique_ptr<WP2::SymbolWriter> sw(new (WP2Allocable::nothrow)
                                             WP2::SymbolWriter);
   ASSERT_TRUE(sw != nullptr);
-  ASSERT_WP2_OK(sw->Init(info));
+  ASSERT_WP2_OK(sw->Init(info, /*effort=*/5));
   ASSERT_WP2_OK(sw->Allocate());
   ANSEnc enc;
   ANSDictionaries dicts;
@@ -990,7 +990,7 @@
                  /*num_clusters=*/1, StorageMethod::kAuto);
 
     SymbolWriterForTest sw;
-    ASSERT_WP2_OK(sw.Init(info));
+    ASSERT_WP2_OK(sw.Init(info, /*effort=*/5));
     ASSERT_WP2_OK(sw.Allocate());
     sw.AddTrivial(kSymbol, kCluster, kValue);
 
@@ -1040,7 +1040,7 @@
                StorageMethod::kAuto);
 
   SymbolWriterForTest sw;
-  ASSERT_WP2_OK(sw.Init(info));
+  ASSERT_WP2_OK(sw.Init(info, /*effort=*/5));
   ASSERT_WP2_OK(sw.Allocate());
   sw.AddRange(kSymbol, kCluster, /*mapping=*/nullptr, /*size=*/0,
               kMaxSymbol + 1);
@@ -1096,7 +1096,7 @@
   }
 
   SymbolWriterForTest sw;
-  ASSERT_WP2_OK(sw.Init(info));
+  ASSERT_WP2_OK(sw.Init(info, /*effort=*/5));
   ASSERT_WP2_OK(sw.Allocate());
 
   ANSDictionaries dicts;
@@ -1160,7 +1160,7 @@
   }
 
   SymbolWriterForTest sw;
-  ASSERT_WP2_OK(sw.Init(info));
+  ASSERT_WP2_OK(sw.Init(info, /*effort=*/5));
   ASSERT_WP2_OK(sw.Allocate());
   ANSDictionaries dicts;
   ASSERT_WP2_OK(sw.AddGolomb(kSymbol, kCluster, golomb_histogram.data(),
@@ -1215,7 +1215,7 @@
   const uint32_t p0 = 3, p1 = 10;
 
   SymbolWriterForTest sw;
-  ASSERT_WP2_OK(sw.Init(info));
+  ASSERT_WP2_OK(sw.Init(info, /*effort=*/5));
   ASSERT_WP2_OK(sw.Allocate());
   ASSERT_WP2_OK(sw.AddAdaptiveBit(kSymbol, kCluster, p0, p1));
 
@@ -1267,7 +1267,7 @@
                  StorageMethod::kAdaptiveSym);
 
     SymbolWriterForTest sw;
-    ASSERT_WP2_OK(sw.Init(info));
+    ASSERT_WP2_OK(sw.Init(info, /*effort=*/5));
     ASSERT_WP2_OK(sw.Allocate());
     ASSERT_WP2_OK(sw.AddAdaptiveSymbol(
         kSymbol, kCluster, (ANSAdaptiveSymbol::Method)method, speed));
@@ -1330,7 +1330,7 @@
   ASSERT_TRUE(sw != nullptr);
   ANSEnc enc;
   ANSDictionaries dicts;
-  ASSERT_WP2_OK(sw->Init(info));
+  ASSERT_WP2_OK(sw->Init(info, /*effort=*/5));
   ASSERT_WP2_OK(sw->Allocate());
   constexpr uint32_t max_nnz = 10000;
   ASSERT_WP2_OK(sw->WriteHeader(/*sym=*/0, /*cluster=*/0, max_nnz, counts,
diff --git a/tests/test_syntax_writer.cc b/tests/test_syntax_writer.cc
index db86219..5c41deb 100644
--- a/tests/test_syntax_writer.cc
+++ b/tests/test_syntax_writer.cc
@@ -68,6 +68,7 @@
 
   // Instances that will be needed after 'syntax_writer' is deleted.
   Vector<CodedBlock> cblocks;
+  Vector_u16 size_order_indices;
   FrontMgrLexico mgr;
   float cost, cost_dicts;
   uint32_t num_tokens;
@@ -90,6 +91,9 @@
       ASSERT_TRUE(mgr.TryGetNextBlock(kBlocks[i], &block));
 
       ASSERT_TRUE(cblocks.resize(cblocks.size() + 1));
+      ASSERT_TRUE(size_order_indices.resize(cblocks.size()));
+      std::iota(size_order_indices.begin(), size_order_indices.end(), 0);
+
       CodedBlock& cb = cblocks.back();
       cb.is420_ = false;
       cb.id_ = AssignSegmentId(config, gparams, tile_rect, block);
@@ -137,7 +141,8 @@
     ANSEnc enc;
     mgr.Clear();
     ASSERT_WP2_OK(syntax_writer.WriteHeader(&enc));
-    ASSERT_WP2_OK(syntax_writer.WriteBlocks(cblocks, &mgr, &enc));
+    ASSERT_WP2_OK(
+        syntax_writer.WriteBlocks(cblocks, size_order_indices, &mgr, &enc));
 
     cost = enc.GetCost();
     cost_dicts = enc.GetCost(dicts);
@@ -151,7 +156,8 @@
   ANSEnc enc_copy;
   mgr.Clear();
   ASSERT_WP2_OK(syntax_writer_copy.WriteHeader(&enc_copy));
-  ASSERT_WP2_OK(syntax_writer_copy.WriteBlocks(cblocks, &mgr, &enc_copy));
+  ASSERT_WP2_OK(syntax_writer_copy.WriteBlocks(cblocks, size_order_indices,
+                                               &mgr, &enc_copy));
 
   ASSERT_EQ(cost, enc_copy.GetCost());
   ASSERT_EQ(cost_dicts, enc_copy.GetCost(dicts_copy));