<table border="1" cellspacing="0" cellpadding="8">
    <tr>
        <th>Issue</th>
        <td>
            <a href=https://github.com/llvm/llvm-project/issues/155410>155410</a>
        </td>
    </tr>

    <tr>
        <th>Summary</th>
        <td>
            [libc++][perf] Is there a reason appending to `std::string` is much slower than to `std::vector<char>`?
        </td>
    </tr>

    <tr>
      <th>Labels</th>
      <td>
            libc++
      </td>
    </tr>

    <tr>
      <th>Assignees</th>
      <td>
      </td>
    </tr>

    <tr>
      <th>Reporter</th>
      <td>
          toughengineer
      </td>
    </tr>
</table>

<pre>
    # tl;dr:
Considering `std::string` is (supposed to be) tailored to handle characters, and implements small buffer optimization which should make appending to it a bit cheaper even for considerable eventual size of the resulting string, appending to `std::string` is significantly slower than inserting at the end into `std::vector<char>`.
Is there a particular reason for that?

# Benchmark results
I ran benchmarks inside Ubuntu 24.04.1 LTS inside WSL on i5 13600k CPU using latest Clang 20.1.

I used option `--benchmark_min_warmup_time=0.1` to make results more stable so the benchmark was launched like this:
```
./bench --benchmark_min_warmup_time=0.1
```

The benchmarks looks like this:
```c++
void vector_push_back(benchmark::State& state) {
  for (auto $ : state) {
    std::vector<char> v;
    //v.reserve(Count);
    for (size_t i = 0; i != Count; ++i) {
      v.push_back('0');
 benchmark::DoNotOptimize(v);
    }
  }
  state.counters["rate"] = benchmark::Counter{ static_cast<double>(Count),
 benchmark::Counter::kIsIterationInvariantRate };
}
```
I.e. a chunk of characters is appended to a container `Count = 1000` times, in this example the chunk is just one character.

I compared **rate** as reported by the benchmarks which is amount of bytes appended divided by CPU time.
<details><summary>Here is the complete code. (Click/tap to expand.)</summary>

```c++
#include <vector>
#include <string>
#include <string_view>
#include <cmath>

#include <benchmark/benchmark.h>

using namespace std::string_view_literals;

const auto data =
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"012345678901234567890123456789012345678901234567890123456789"
"0123"sv;

const size_t Count = 1000;

void dummy(benchmark::State& state) {
  for (auto $ : state) {
    size_t s = 0;
    for (size_t i = 0; i != Count; ++i) {
 ++s;
      benchmark::DoNotOptimize(s);
    }
  }
 state.counters["rate"] = benchmark::Counter{ static_cast<double>(Count),
 benchmark::Counter::kIsIterationInvariantRate };
}

void vector_push_back(benchmark::State& state) {
  for (auto $ : state) {
 std::vector<char> v;
    //v.reserve(Count);
    for (size_t i = 0; i != Count; ++i) {
      v.push_back('0');
      benchmark::DoNotOptimize(v);
 }
  }
  state.counters["rate"] = benchmark::Counter{ static_cast<double>(Count),
 benchmark::Counter::kIsIterationInvariantRate };
}

void vector_insert(benchmark::State & state) {
  const auto d = data.substr(0, state.range());
  for (auto $ : state) {
    std::vector<char> v;
 //v.reserve(d.size() * Count);
    for (size_t i = 0; i != Count; ++i) {
 v.insert(v.end(), d.begin(), d.end());
 benchmark::DoNotOptimize(v);
    }
  }
  state.counters["rate"] = benchmark::Counter{ static_cast<double>(state.range() * Count),
 benchmark::Counter::kIsIterationInvariantRate };
}

void string_push_back(benchmark::State& state) {
  for (auto $ : state) {
 std::string s;
    //s.reserve(Count);
    for (size_t i = 0; i != Count; ++i) {
      s.push_back('0');
      benchmark::DoNotOptimize(s);
    }
 }
  state.counters["rate"] = benchmark::Counter{ static_cast<double>(Count),
 benchmark::Counter::kIsIterationInvariantRate };
}

void string_append(benchmark::State & state) {
  const auto d = data.substr(0, state.range());
  for (auto $ : state) {
    std::string s;
 //s.reserve(d.size() * Count);
    for (size_t i = 0; i != Count; ++i) {
 s.append(d);
      benchmark::DoNotOptimize(s);
    }
  }
 state.counters["rate"] = benchmark::Counter{ static_cast<double>(state.range() * Count),
 benchmark::Counter::kIsIterationInvariantRate };
}

constexpr size_t RangeMultiplierSquared = 2;
constexpr size_t DataSize = 512;

template<typename B>
void Args(B b);

BENCHMARK(dummy);
BENCHMARK(vector_push_back);
BENCHMARK(vector_insert)->Apply(Args)->Arg(DataSize * 2);
BENCHMARK(string_push_back);
BENCHMARK(string_append)->Apply(Args)->Arg(DataSize * 2);

int64_t roundedBefore(int64_t i) {
  return static_cast<int64_t>(round(i * pow(RangeMultiplierSquared, -0.5)));
}
template<typename B>
void Args(B b) {
  b->Args({ 1 });
  int i = RangeMultiplierSquared;
  if (const auto k = roundedBefore(i); k != 1)
 b->Args({ k });
  b->Args({ i });
  while ((i *= RangeMultiplierSquared) < DataSize) {
    b->Args({ roundedBefore(i) });
    b->Args({ i });
  }
 if (const auto k = roundedBefore(i); k < DataSize)
    b->Args({ k });
 b->Args({ DataSize });
};
```
Here `dummy` is just a control to gauge problems.

`Args` function template sets up sizes so that there is an additional data point roughly between powers of two on a logarithmic scale.

For some reason the last run gives incorrect measurements sometimes, I added sacrificial data points at the end to mitigate that (`DataSize * 2`).
</details>

## Results
![Image](https://github.com/user-attachments/assets/0ee4cc86-febe-4f93-97fb-58a91101ff61)
<details><summary>Full benchmark output. (Click/tap to expand.)</summary>

```
2025-08-26T14:12:56+03:00
Running ./bench
Run on (20 X 3494.4 MHz CPU s)
CPU Caches:
  L1 Data 48 KiB (x10)
  L1 Instruction 32 KiB (x10)
  L2 Unified 2048 KiB (x10)
  L3 Unified 24576 KiB (x1)
Load Average: 0.00, 0.03, 0.08
-----------------------------------------------------------------------------
Benchmark Time             CPU   Iterations UserCounters...
-----------------------------------------------------------------------------
dummy 227 ns          209 ns      3356710 rate=4.77757G/s
vector_push_back          530 ns          489 ns 1420428 rate=2.0451G/s
vector_insert/1          1246 ns         1151 ns 608950 rate=869.157M/s
vector_insert/2          1485 ns         1371 ns 510698 rate=1.45902G/s
vector_insert/3          1637 ns         1511 ns 464718 rate=1.98556G/s
vector_insert/4          1907 ns         1760 ns 399303 rate=2.2721G/s
vector_insert/6          2219 ns         2049 ns 340955 rate=2.9287G/s
vector_insert/8          1903 ns         1757 ns 397994 rate=4.55447G/s
vector_insert/11         2451 ns         2263 ns 309396 rate=4.86143G/s
vector_insert/16         2710 ns         2501 ns 279629 rate=6.39634G/s
vector_insert/23         4064 ns         3751 ns       186611 rate=6.13099G/s
vector_insert/32         1851 ns 1708 ns       409604 rate=18.7305G/s
vector_insert/45         4019 ns 3710 ns       188916 rate=12.1305G/s
vector_insert/64         3436 ns 3172 ns       220936 rate=20.1796G/s
vector_insert/91        23445 ns 21476 ns        33860 rate=4.2373G/s
vector_insert/128       36662 ns 33703 ns        20895 rate=3.7979G/s
vector_insert/181       61004 ns 55933 ns        12640 rate=3.23601G/s
vector_insert/256       90194 ns 83002 ns         8457 rate=3.08425G/s
vector_insert/362      137841 ns 127006 ns         5485 rate=2.85025G/s
vector_insert/512      203347 ns 187528 ns         3777 rate=2.73027G/s
vector_insert/1024     443245 ns 409018 ns         1713 rate=2.50356G/s
string_push_back         1645 ns 1519 ns       459028 rate=658.405M/s
string_append/1          4398 ns         4060 ns       171797 rate=246.306M/s
string_append/2 4295 ns         3965 ns       175805 rate=504.405M/s
string_append/3 4357 ns         4022 ns       175788 rate=745.915M/s
string_append/4 4348 ns         4013 ns       174600 rate=996.641M/s
string_append/6 4286 ns         3956 ns       176163 rate=1.51661G/s
string_append/8 4386 ns         4048 ns       173727 rate=1.97618G/s
string_append/11 4377 ns         4040 ns       172980 rate=2.72269G/s
string_append/16 4393 ns         4056 ns       171781 rate=3.94523G/s
string_append/23 4619 ns         4264 ns       165310 rate=5.39399G/s
string_append/32 4567 ns         4215 ns       165359 rate=7.59122G/s
string_append/45         4509 ns         4162 ns 167575 rate=10.8112G/s
string_append/64         5094 ns         4702 ns 150211 rate=13.6121G/s
string_append/91         6280 ns         5797 ns 120507 rate=15.6978G/s
string_append/128        7148 ns         6598 ns 105000 rate=19.3984G/s
string_append/181       10105 ns         9328 ns 75234 rate=19.4047G/s
string_append/256       11624 ns        10730 ns 65008 rate=23.8591G/s
string_append/362       15333 ns        14153 ns 49536 rate=25.5773G/s
string_append/512       19747 ns        18228 ns        38433 rate=28.0885G/s
string_append/1024     449037 ns 414404 ns         1705 rate=2.47102G/s
```

</details>

On the right some crazy things happen to `std::vector<char>` which I can not fully explain (probably data set exceeding L3 size plays a big role), instead let's focus on "the good part" on the left.

![Image](https://github.com/user-attachments/assets/cd51d118-1262-416c-be9c-db084fd79dda)
Here `std::string` is **68% slower** than `std::vector<char>` regarding `push_back()`;
and around **40% to 70% slower** regarding appending characters in chunks depending on the chunk size (using vector `insert()` and string `append()`).

One can think that maybe different allocation patterns of `std::string` and `std::vector` play a role, and indeed they do play a role, so instead of investigating it I eliminated it altogether by reserving necessary storage (uncommented `reserve()` calls).

## Results with `reserve()`
![Image](https://github.com/user-attachments/assets/033fb02b-e200-4b07-b7f2-d7ad04b15226)
<details><summary>Full benchmark output. (Click/tap to expand.)</summary>

```
2025-08-26T14:19:29+03:00
Running ./bench
Run on (20 X 3494.4 MHz CPU s)
CPU Caches:
  L1 Data 48 KiB (x10)
  L1 Instruction 32 KiB (x10)
  L2 Unified 2048 KiB (x10)
  L3 Unified 24576 KiB (x1)
Load Average: 0.40, 0.20, 0.13
-----------------------------------------------------------------------------
Benchmark Time             CPU   Iterations UserCounters...
-----------------------------------------------------------------------------
dummy 226 ns          208 ns      3361395 rate=4.79691G/s
vector_push_back          439 ns          405 ns 1726001 rate=2.46609G/s
vector_insert/1           997 ns          920 ns 764443 rate=1.0865G/s
vector_insert/2          1230 ns         1135 ns 610784 rate=1.76206G/s
vector_insert/3          1440 ns         1330 ns 524039 rate=2.25626G/s
vector_insert/4          1572 ns         1451 ns 487840 rate=2.75743G/s
vector_insert/6          2006 ns         1852 ns 374242 rate=3.24026G/s
vector_insert/8          1560 ns         1440 ns 481767 rate=5.55526G/s
vector_insert/11         2199 ns         2030 ns       345151 rate=5.41978G/s
vector_insert/16         2424 ns 2238 ns       313185 rate=7.15004G/s
vector_insert/23         3759 ns 3470 ns       200937 rate=6.62851G/s
vector_insert/32         1114 ns 1028 ns       678571 rate=31.1138G/s
vector_insert/45         3111 ns 2872 ns       244499 rate=15.6689G/s
vector_insert/64         2012 ns 1857 ns       379973 rate=34.4563G/s
vector_insert/91         5697 ns 5259 ns       133134 rate=17.3044G/s
vector_insert/128        3046 ns 2812 ns       249728 rate=45.5218G/s
vector_insert/181        6762 ns 6241 ns       111984 rate=28.9997G/s
vector_insert/256        5731 ns 5290 ns       117313 rate=48.3906G/s
vector_insert/362        9787 ns         9034 ns        83134 rate=40.0707G/s
vector_insert/512 7479 ns         6904 ns       101189 rate=74.1609G/s
vector_insert/1024 13777 ns        12717 ns        55029 rate=80.5231G/s
string_push_back 1453 ns         1342 ns       524367 rate=745.401M/s
string_append/1 4704 ns         4343 ns       160682 rate=230.276M/s
string_append/2 4347 ns         4013 ns       173773 rate=498.401M/s
string_append/3 4346 ns         4011 ns       174178 rate=747.867M/s
string_append/4 4421 ns         4081 ns       172674 rate=980.083M/s
string_append/6 4434 ns         4093 ns       166934 rate=1.46594G/s
string_append/8 4400 ns         4062 ns       173671 rate=1.96953G/s
string_append/11         4420 ns         4080 ns 174113 rate=2.6962G/s
string_append/16         4499 ns         4153 ns 169456 rate=3.85279G/s
string_append/23         4542 ns         4193 ns 167624 rate=5.48576G/s
string_append/32         3922 ns         3620 ns 191241 rate=8.83886G/s
string_append/45         3935 ns         3633 ns 192302 rate=12.3881G/s
string_append/64         3948 ns         3644 ns 193690 rate=17.5631G/s
string_append/91         4459 ns         4116 ns 170266 rate=22.1066G/s
string_append/128        4470 ns         4126 ns 169312 rate=31.0209G/s
string_append/181        6626 ns         6116 ns       119231 rate=29.5937G/s
string_append/256        5613 ns 5181 ns       134655 rate=49.4099G/s
string_append/362        8021 ns 7404 ns        95064 rate=48.8942G/s
string_append/512        9513 ns 8781 ns        79907 rate=58.3047G/s
string_append/1024      14779 ns 13642 ns        51335 rate=75.0635G/s
```

</details>

Here you can see that `std::vector<char>` _really_ doesn't like chunk sizes different from powers of two, but overall it is still mostly faster than `std::string` (unless it becomes really unlucky).

Again let's look at "the good part" with less influence from weird effects.
![Image](https://github.com/user-attachments/assets/42fcac87-df47-4f4f-a6f6-40e087bd6c6f)
This picture is very similar to the one earlier.

Here `std::string` is **70% slower** than `std::vector<char>` regarding `push_back()`;
and around **45% to 80% slower** regarding appending characters in chunks (depending on the chunk size).
Removing memory allocation overhead revealed even larger difference in performance between `std::string` and `std::vector<char>`.

## Quick-bench
Here's another evidence that appending to `std::string` is significantly slower than to `std::vector<char>`:
https://quick-bench.com/q/EKUKhSTDZ3wKF866p1ZiUgRESI8

There it shows appending to `std::string` is from around 16% to two times slower than inserting at the end of `std::vector<char>`.
(Although it uses Clang 17 which is not the most recent, the latest version is not available.)

# For context
I discovered this issue while researching this:
https://github.com/simdjson/simdjson/pull/2408
</pre>
<img width="1" height="1" alt="" src="http://email.email.llvm.org/o/eJzsXFtz4zaW_jXsF5RYuF8e_CDb7RlXkpnd7qR2a15cEAlJHFOkhgDldn79FgCKBGXT3b3pnkpq0pWKJZH8cHBwrh8gaWurXWPMVcauM3b7Tvdu33ZXru13e9PsqsaY7t2mLZ-vMkyAqzNyXXYZWWdwfdM2tipNVzU7kHFoXek_J2vr_EcZh6CyIMPS9sdja00JXAs2JsMKOF3VbRc_2eumrA0o9rrThTOdzfAN0E0JqsOxNgfTOAvsQdc12PTbrelAe3TVofpVu6ptwNO-KvbA7tu-LsFBPxqgj0fTlF4m14LKAQ02lQPF3uij6YA5mQZs2w4Ug_B6U5vwqet1DWz1qwHtFri9AZ2xfe080DAhL1cKvjhnr9FqWxW6cfUzsHX7ZDrg9roBVWNNFzC1C4MYP9HmAuxkCtd2GbnxSsnI-4zDPIPre-sf6QzQ4Kg7VxV9rTvQGW3bOCe31y4jdxn0q-OX69o0xf6gu8dhMtajgE43YHO-Yr1MVWnAL5u-cT3ANIc0R-DHnz-er_zPxx9B24CKAUQ4hI_g5r9-Ab31s6i1M9aBm1o3O4BhjvI4-D3o_YL7pWobP7fVahzx4VA1D0-6O_THB1cdTEZuYY686lwbl3AQFhzazgDrwhLZNuhrRAFP2oJa902xNyWoq0cD3L6y0TIzDof_4DrP8F14CnxehvmTGVz_nA5pQd22_v8LgxUZvvb_wfWprUoQV_Hh2Nv9w0YXjxmWI1Rc549OO5Nh7ufogmNkwj8OwmpmWOreWwamICPrlzcBsGgx4JSR800Zvsvw3SnvjDXdyWRY3rR94zKspnuG8bz5PzhQgYzcApiRa_8SI_8uPkOuQZxkNRcEgFOeTjTDAmZYTGNczPy2_Vvr_h4d2Ut0mkmTidvw6vw3zDwvvAQ-PrDrDOMuKANn7DYIe4F_E-_NxHV4uCoeCm1dRm7Ktt_UxvtUogZ884qIZ4jw5vHe3jvThZhz35x0V-nGfdDOBBmD4FHY1H7uc5MDDYp93zz6oDKFOB8kYiiJMVD7cOR01ZjOO0sYOkwLQQiDZ1QHEwJj1QTLA-aT9uEx-EQcoLLgn711oG2SYDq6Y9EejtpH3AyvM7yO2vOvgLagM8e2c6YEm-e5k9khvnpxD0Godgs2z84k4pfVqSrjsz4ueEnDqOSmND7OW69scmP7w0F3zxl5_1cfwiobRW_9LJx_UZrcm-BNXXkLunP66DVjPh11U-bBPG4yfDfBDFHupfNlmFRNUfelARm5OXvG-xdXhpC9eOXhVJmn1y4XB-32kwSza5MRDVHHv86nu2PYbPTB2KMuDLhIIGHMh7rytuY1dx2f8snKgRANSu20N40wMoYIE8q4kOrrX3nv-RPkT5A_Qf4E-eYgGcb2dBG_huriIrud7wk1U9kfDs_fo1CKQ9uxsPkWdU_8xE5o4HNljn2rzPkjVTn_phL3j1PffsnqJ0XuH7nCna997GgXFh68uvJpNROm5mua3PYb67oMS-jr3KiQTjc7E9StElV_s-bopeWUuY1rFaDwGnxjUzrlo75OuWnK89xuQJlvzK5q0g-S67_nNurFUs0V9x1Mb6iVv3_YiQMBexls7HcONva3B5tXU80fONwMax7bzt91uLkwmpcW872DjM1HLZXfwGi-c33ybw0fwRbMp2N3rgg_-HF_6mtXHevKdB__1UeWhNwCHJ998citdvpj9asJNzGExwrWmcOx9gohN-75aHyTD65j5x9MeN3tbIblNdiMas7g-vr9327--tP6ww9-uWL5O1xMr7ystJZvOqcYtcrI-_XxWPuKOg4eP-p2GZbTNPAa4FfxXsbZ5ZvOJvf_GjSD66pxnD440LV9U5ry2mzbzhvE-fNZjOyM67vmwqiGO6NVBRj_eBjp2D5lWL6-1t79VzBng9OPIgWT-aolHcXbDBP2l7zxo2CJk2tVzdmnF0Q637f1QSCJX4_hoRcqitD-aowNyH_gveZCjscLOS6vVxfXn_ZV7ZdKnhX5hsh--uRmdI55bLwc6LUZXIz9eemGsPT1SpqLuTDaha4uL0-GnNw1xZuEBQ50Z8Zh9O24QRSI2kj6dm0NXAt2ut8ZcOzaTW0ONh_JzTAkh2DbN0XYSzlbJLDGWdAfQ1SycXsk7ilFdlU3QJdl5Z_RdWQNj603vK7td_v6GWyMezKm8b5hOhs2vZ5a0DZAg7rd6a5y-0NVAFvo2gzy3LUdsO3BnPec3N6AWlsHur4Bu-pkLKiaou06UzhwMNr23XkDrz2YkcO-95KZElhddNW2KqqZgDbdGnMtOFSu2vkJh_l59XN4EUY4zLAaSOcM3yW885mhzTABH8YtMO8m7Pr-oHcmY7cZlnvnjmE3J-TqXeX2_SYv2kOG73prupV2Thf7MJUM32nrdZ_hO2gMLQrJV1uzMSu6VWSlxHazYlIrhCDabvngists-F1f18muVtu7Y-9-ExOewTWGmK2gXGH-M6IZWfsktWY8w9eQZGQN_T0f-qbxdcq4PxY_8xaQYYkh-F9AqKI5BT_99ddA7ds4F__yRhd7M-x_AfAjCv4AqAQ_VL4ikZ8QPLvWjwjcN9Z1fbRfgl-7B4NfmmpbmRJg-DoKme6gTPDplnjHj60uwfpkOr-mZA1gDkNhB3NIhr8yg-vVt_znE-G4cD9XBwPSf15NAIyViQW_WNMNhYvN8_w7SBNCDMBYgMZOgmCoxveEMC4QBKFuI7c0F0Iw8RdvTj6lXRQZEwYjcIZJZcBEFEOK5RkO55AydIF2rkbu0PQ4wpSneAgx5N9zKBUbpZNc5YiJnxbwcIJHJZvhERHwGIJcjeKhnDIF8ZJ8JMHjZKZDxFDAo5wKlOApyRhfwqMJnoJzPMGDPolSBJJJfVjgRfXxZEUxUikchjS8JxQqxiY4heXl2o5wciYdmUvHRJROKEUnW2GM0kU8NC0vpnE1J3F5wCdQEcUnPMkRJYt403yxt9gUj8GAj4XiWJ3xeE4UJ3QJD0_LSyGnKR4RqbxIco7QhIoIVGrRZiYjRDKiIAHlhEah4nDUIZK5IJAtWgxLZIxLTGZzR1IqNGoQYS_cIhqfDJBQEvyNIIEnNIyhIiMahjkSatGa1bi-mFAavA0jKlI3JkTyJLRgIpZXF5_tj3DOg0yEiJkZYh8LzmgkF0osrgKSZ-E4gjCsLWOKpGgIcwonNEw4XPQ0zM62pyBSAU4SCHFqM5IyMcFBSfHiOhA-GAkiQtJoI1hAOIuAzEew0XElg8t4DOGzhgihwVORFAzLuU0LMeEJAvGy50IcLYVSguPKUqggkvOYgJI4xSCZ4t5llzg9wyMaYmm4CjF4jKGcyZxC9tMc69xMpjmDEjWTiEKeeoZAQk0zpjwnkC-hYkCxmiUMojhLwZiE43IwSN8SkQBKmJhLhvEMTMhxvoKyXKFFMAoooRfTRCQFoxyOdqwUzzlFS2AcUCz5fJqMp2AccTJlM4Y4R39ZAJOAkjkYhamkSBCBRZIaBUdyCQwhQIm4UBqdLSdWEiYGjDFXi2jcGweZo80nioREk7sqyjBZQsMEUD7PrxSnCQNxRqYSiuVEEbUoG8GAMj6fKUZsjsbGJCZyphDGS2hpgmBwLiOKYRRxwcRouwjmEqFFvCRFMKhmSZGKGPAQg3hKh4jkHOFFI5mSBOBYzpI28-4ZQh9kcLITlnMllu1kTBNAoLlfcBbDAYIMTh6BVE6UpIt4Y6JAEMFZBFAkBlDBMKEJHIVTzfPCVMZEgRDHqfoQFLFi5gzCqT4muWRqUXtjogCIkXn2ooiF91SxJGOznAmxaMhjngBICZqaIJJ4li2IpGSK7jKHUrJFFU7ZQsFYJVNEKaTzbAGTbEYFmqruiwOWr3fsf4_cQlft9i4yDkWnf30Gbl81Owv2QZrLI7ivnJodjq_dg0I3oGkd2PZ1_exb6VpXoc89du1Gb-rnSEBY44D5VBgTjvj-SOJ54GOtn204R7wDXVubYYOuaqwzugS1cRkWFmzborexfcZe-l3bluGgboYxOLMlZuvO5M43IiGKkqESIblCmOMVRbxYbYwqVuUGSrothSpLHXvkMxO1dFB7neE1lxlmw5Hl4YBgOLj8OUV3Zqe7cjgJPt-8Un6xAy2mmxLoQMwNo1HoR3MtEPDFsBPidOg6PUHZxHOPFpTmfHlQcjwPaSM_JOOJuyizF27cgI2ShaPmw5ZNxuG4czLIPfBK3iRNsCJvgo-Rizro540BZbXdms40Dui6bot4LP2onTNdE2i11zXuh31FqRwGcwN6sLThKHxTGlP6yT2Dsr28w7ajLbZbUDUnYwNl5qdUOXAPTF0dqkY7U4YD8bVrd8btTQc2zyBuS4VDiaYw1uruGVjXdnoXtdcU7cHbnAniTptYg_YKXdd20tKcagNPldu_9ti35OAI2W4g3qwMhnBFN1CsNmKLV6XQJaQbxDDmvw8OTmVk7SPHfxAHRwcODg9_EfmP4eD4BQcnEw6OIzK1tjQXiqvLZvQVDo4SNefgYgGDBOYQoiTdcg6XW-VEO0rNiUKFQ8kiOKU0aQug5Iu9aMrB4TlHiBAJ8nEEhZwqqlxwDBdZhpSDo3SORyI-wxQSlXBmjOMv4uCYmHXwaGCpqBSSpq0GE8usVErCXXTwSLLIYwiKKU6oBgqX5UtZOMbn8x3mTyUSXEztBmNsGS9l4ZC6IAnT9SGUIYYmVIqSMvwtLo7GOhdjklSQBBE08RciRwzCL2HiiGADcSkS2TCEioiJgeNYLrPKKQOHEI0tQVrdciGZmJo_lCNEFmeaNFgERc4XyxlnRilVKm1fuFx0tqS9whDFdkqmXAERSonR1QjNKeOLtpc0V4xH12WYJWuMCEFJ7yJyAuniKiStFYGRjccSzWaqxMTTUJYzjJYtZGysABexEeWYpqwqQmqKAljmSqlFRmrqqwATJDL5WKX0ABJk4qOozIl6I6aMbRVQQs5CnoIk7Vtkqj8Kcyjgooy-uxJUzFyMq7QNQhAhOXX2NEdvxWXfVqHA2qWkpUDpe8bgxHdLmDNMLpvJKW8gyuasPqHJ6jJMyRRVBGU5hYtEEgJUzBs8SmjKSnHIJZ66XJhj8Rb5Rqh4i-IiYvIJquRbkhEPdsFKITTjy5BIyDeRSy7eIN8oRnMwOQPDXIz2oSTMoSRvkG-UzHUG1UxnXCXOmlPO1CJvIQGlEF6wnzOOkfApxqFcccUWeYEkR1CKL1AjaePVlvK9XPFFDilJDiE0zjipaIKIK8r4lBIlw2KRMUs3ahjFczw14AmOaZK8JBOXdHTKwJ3_EYVneITH6SOFfLA6O1YuiZSLeGmKUGTOIvPI2iCFCcTJNg2RcpH1Sbdp1JzjIpzGhKYIVzAJ7Iy_cPzXODhK2cV6IB6XF2I-0Ug4R5AvzjdJFHSWpj1eLHMRVwThJMVCDJfZ2iRV8HmZzAf5BqP2apzqWpUzRb6AigOMx3DC0Mx5CeXT3ihVOYVvsLZTypAwhgQxp7gUg5wmCUgquughExEHFIuySZHKBoRSEyfKpE_cizMdCTiAqIj5BxE-cxSGCJmqMZZDTthX8W-BKXpu-8B4WHM-e_MZDuihM7qunx9A2RrbZFi4-FXpiY-xCVuy7drD_PCR7xE3vQOtbyHrGlQufJneVXUNDq119TPYauvO36V_nVcJnEVtrPWPb0zRHowFUTDQN3VfPD5PdMV6p6tmJPDqtn0E4YzRS_4uMBkRttnWvWkKE2fwZKquBGa7NYWL57a-EbFB8bbQhRSrckvFim7pdqX5lq8oNFCKTckLvo1998_7yoJjVbg-Hv06me4Z2OpQ1boDLn5vvm0MMLqrq_E7wV_CBr5Cy31PNpANbKD8DWxghuUbhOCw9B_MoQ2c18Ec2u45Ze688e2NLkFnTkbXpow_GVHrbme60XwL44c8mm7bdgft356P030V23f5Cw8Tg_bffVU8rs6ckF-rYKK6aQNxZ05VGaQIjvlbf5Xi80R6ZJnmlvyvScTBnP-V4bv3P_zyw_7jz7f_IE8_3EnOj-gf1S-7D-8_3svxJxW8mTpg9-2T_ULZg6sNxoL4YCbuqY1fjP_8T2xc0LBL6sdyXbt92-_2XsDeGjv8tAUS0zfhmzbi-pAEOlOYxvnIFQ9Dhp_DOJnOelsabtYnXdV6U5t8ICLjj3PcxV8hceaTC1_RLytbeOsLRG9lQWVtb4YzuJ2xRnfFPuhp_OWJNyKLrQ7lP23bzF8e-7r2yZJC-a68IqUiSr8zV0gwjhGBnLzbX22QgEYVEG8U0QxqyATdaGQI1oJSVr6rrjDEDErMEWaCkpwWhuMtQ5ARXZQcZxSag67qvK5Ph7ztdu_CVK4QYxTBd7XemNpexS8Q1NXm_M398DWCd92Vf2q16Xc2o7CurLMTjqtcHX6pJnmM3Wbs2ntixm5B8hMpwxnVL7OvQ1_sv9ol7t71XX31xiJ4uYc_q2PX_tMUvtULyvARftDH6Qr_XwAAAP__-7zuFw">