[frameworks/kio] /: UDSEntry use two vectors to store fields value

Méven Car <[email protected]>
Newsgroups gmane.comp.kde.cvs
Message-ID <[email protected]>
Git commit a7b7e249eded900d92aa2240793b40d7e1a65b8d by Méven Car.
Committed on 27/07/2026 at 08:07.
Pushed by meven into branch 'master'.

UDSEntry use two vectors to store fields value

Use separate vectors to store number values and string values.
sizeof UDSEntryPrivate grows from 4 to 7 bytes, but with each value in the entry, we save 1 byte previously unused (either saving sizeof QString or sizeof long long).
Realistic entries have at least three entries which means we never increase memory consumption, but for any entry with more fields we save 1 byte per-field.
This trickles down in lesser memory used, less allocations, slightly
faster access, provided the two internal vectors are accessed in
sequence, rather than alternating between number and string keys.

This shouldn't constitute a binary breakage since UDSEntry `operator<<` and `operator>>` are kept using the same format, allowing KIO to receive data from workers with a different implementation.

The format on the wire, could be optimized but probably best to wait for KF7...

https://invent.kde.org/frameworks/kio/-/merge_requests/1593 has shown potential perf improvements.

`# echo "0" | sudo tee /proc/sys/kernel/perf_event_paranoid // might need that to be allowed to use perf`

After:

<details><summary>5900X</summary>

```

ninja && ./bin/udsentry_benchmark -perf -iterations 100

[2/2] Generating mo...
********* Start testing of UDSEntryBenchmark *********
Config: Using QtTest library 6.6.3, Qt 6.6.3 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 13.2.0), ubuntu 23.10
PASS   : UDSEntryBenchmark::initTestCase()
PASS   : UDSEntryBenchmark::createSmallEntries()
RESULT : UDSEntryBenchmark::createSmallEntries():
     1,159,602.63 nsecs per iteration (total: 115,960,264, iterations: 100)
     4,955,078.99 CPU cycles per iteration, 4,27 GHz (total: 495,507,899, iterations: 100)
     9,685,675.17 instructions per iteration, 1,955 instr/cycle (total: 968,567,518, iterations: 100)
     1,582,949.28 branch instructions per iteration, 1,37 G/sec (total: 158,294,928, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntries()
RESULT : UDSEntryBenchmark::createLargeEntries():
     1,265,403.48 nsecs per iteration (total: 126,540,349, iterations: 100)
     5,409,500.73 CPU cycles per iteration, 4,27 GHz (total: 540,950,073, iterations: 100)
     9,618,312.77 instructions per iteration, 1,778 instr/cycle (total: 961,831,278, iterations: 100)
     1,582,225.94 branch instructions per iteration, 1,25 G/sec (total: 158,222,595, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntriesOrderedInsert()
RESULT : UDSEntryBenchmark::createLargeEntriesOrderedInsert():
     839,336.48 nsecs per iteration (total: 83,933,649, iterations: 100)
     3,588,025.72 CPU cycles per iteration, 4,27 GHz (total: 358,802,572, iterations: 100)
     8,514,333.35 instructions per iteration, 2,373 instr/cycle (total: 851,433,336, iterations: 100)
     1,357,033.11 branch instructions per iteration, 1,62 G/sec (total: 135,703,311, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromSmallEntries()
RESULT : UDSEntryBenchmark::readFieldsFromSmallEntries():
     62,859,590.77 nsecs per iteration (total: 6,285,959,077, iterations: 100)
     268,718,709.69 CPU cycles per iteration, 4,27 GHz (total: 26,871,870,970, iterations: 100)
     581,958,970.88 instructions per iteration, 2,166 instr/cycle (total: 58,195,897,089, iterations: 100)
     96,994,734.48 branch instructions per iteration, 1,54 G/sec (total: 9,699,473,448, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromLargeEntries()
RESULT : UDSEntryBenchmark::readFieldsFromLargeEntries():
     832,654.18 nsecs per iteration (total: 83,265,419, iterations: 100)
     3,559,395.58 CPU cycles per iteration, 4,27 GHz (total: 355,939,558, iterations: 100)
     8,175,540.20 instructions per iteration, 2,297 instr/cycle (total: 817,554,020, iterations: 100)
     1,329,768.56 branch instructions per iteration, 1,6 G/sec (total: 132,976,856, iterations: 100)
PASS   : UDSEntryBenchmark::saveSmallEntries()
RESULT : UDSEntryBenchmark::saveSmallEntries():
     364,013.96 nsecs per iteration (total: 36,401,397, iterations: 100)
     1,556,021.93 CPU cycles per iteration, 4,27 GHz (total: 155,602,194, iterations: 100)
     5,541,680.74 instructions per iteration, 3,561 instr/cycle (total: 554,168,074, iterations: 100)
     1,142,040.22 branch instructions per iteration, 3,14 G/sec (total: 114,204,023, iterations: 100)
PASS   : UDSEntryBenchmark::saveLargeEntries()
RESULT : UDSEntryBenchmark::saveLargeEntries():
     182,727.39 nsecs per iteration (total: 18,272,739, iterations: 100)
     781,045.94 CPU cycles per iteration, 4,27 GHz (total: 78,104,595, iterations: 100)
     2,598,754.64 instructions per iteration, 3,327 instr/cycle (total: 259,875,464, iterations: 100)
     546,165.53 branch instructions per iteration, 2,99 G/sec (total: 54,616,553, iterations: 100)
PASS   : UDSEntryBenchmark::loadSmallEntries()
RESULT : UDSEntryBenchmark::loadSmallEntries():
     2,016,898.26 nsecs per iteration (total: 201,689,826, iterations: 100)
     8,621,364.68 CPU cycles per iteration, 4,27 GHz (total: 862,136,469, iterations: 100)
     19,689,953.07 instructions per iteration, 2,284 instr/cycle (total: 1,968,995,308, iterations: 100)
     3,513,732.75 branch instructions per iteration, 1,74 G/sec (total: 351,373,275, iterations: 100)
PASS   : UDSEntryBenchmark::loadLargeEntries()
RESULT : UDSEntryBenchmark::loadLargeEntries():
     1,453,370.78 nsecs per iteration (total: 145,337,078, iterations: 100)
     6,212,990.17 CPU cycles per iteration, 4,27 GHz (total: 621,299,018, iterations: 100)
     14,965,408.40 instructions per iteration, 2,409 instr/cycle (total: 1,496,540,840, iterations: 100)
     2,635,884.45 branch instructions per iteration, 1,81 G/sec (total: 263,588,445, iterations: 100)
PASS   : UDSEntryBenchmark::cleanupTestCase()
Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 9255ms
********* Finished testing of UDSEntryBenchmark *********
```

</details>


<details><summary>Laptop i7-8550U</summary>

```
ninja && ./bin/udsentry_benchmark -perf -iterations 100

[0/2] Re-checking globbed directories...
[89/89] Linking CXX executable bin/kioworkertest
********* Start testing of UDSEntryBenchmark *********
Config: Using QtTest library 6.7.0, Qt 6.7.0 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 13.2.1 20230801), arch unknown
PASS   : UDSEntryBenchmark::initTestCase()
PASS   : UDSEntryBenchmark::createSmallEntries()
RESULT : UDSEntryBenchmark::createSmallEntries():
     166,801.05 nsecs per iteration (total: 16,680,106, iterations: 100)
     537,359.15 CPU cycles per iteration, 3,22 GHz (total: 53,735,915, iterations: 100)
     1,141,490.02 instructions per iteration, 2,124 instr/cycle (total: 114,149,002, iterations: 100)
     248,958.19 branch instructions per iteration, 1,49 G/sec (total: 24,895,819, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntries()
RESULT : UDSEntryBenchmark::createLargeEntries():
     417,137.01 nsecs per iteration (total: 41,713,701, iterations: 100)
     1,402,748.21 CPU cycles per iteration, 3,36 GHz (total: 140,274,822, iterations: 100)
     1,191,994.30 instructions per iteration, 0,850 instr/cycle (total: 119,199,430, iterations: 100)
     232,768.70 branch instructions per iteration, 558 M/sec (total: 23,276,870, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntriesOrderedInsert()
RESULT : UDSEntryBenchmark::createLargeEntriesOrderedInsert():
     58,369.93 nsecs per iteration (total: 5,836,993, iterations: 100)
     163,292.70 CPU cycles per iteration, 2,8 GHz (total: 16,329,270, iterations: 100)
     254,943.33 instructions per iteration, 1,561 instr/cycle (total: 25,494,334, iterations: 100)
     55,565.65 branch instructions per iteration, 952 M/sec (total: 5,556,565, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromSmallEntries()
RESULT : UDSEntryBenchmark::readFieldsFromSmallEntries():
     8,088,293.83 nsecs per iteration (total: 808,829,383, iterations: 100)
     27,690,400.64 CPU cycles per iteration, 3,42 GHz (total: 2,769,040,064, iterations: 100)
     66,044,096.54 instructions per iteration, 2,385 instr/cycle (total: 6,604,409,655, iterations: 100)
     17,971,682.62 branch instructions per iteration, 2,22 G/sec (total: 1,797,168,263, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromLargeEntries()
RESULT : UDSEntryBenchmark::readFieldsFromLargeEntries():
     138,110.02 nsecs per iteration (total: 13,811,003, iterations: 100)
     454,456.53 CPU cycles per iteration, 3,29 GHz (total: 45,445,654, iterations: 100)
     1,103,337.28 instructions per iteration, 2,428 instr/cycle (total: 110,333,728, iterations: 100)
     289,391.50 branch instructions per iteration, 2,1 G/sec (total: 28,939,150, iterations: 100)
PASS   : UDSEntryBenchmark::saveSmallEntries()
RESULT : UDSEntryBenchmark::saveSmallEntries():
     413,747.84 nsecs per iteration (total: 41,374,785, iterations: 100)
     1,395,015.27 CPU cycles per iteration, 3,37 GHz (total: 139,501,527, iterations: 100)
     4,331,349.26 instructions per iteration, 3,105 instr/cycle (total: 433,134,927, iterations: 100)
     908,005.03 branch instructions per iteration, 2,19 G/sec (total: 90,800,503, iterations: 100)
PASS   : UDSEntryBenchmark::saveLargeEntries()
RESULT : UDSEntryBenchmark::saveLargeEntries():
     291,217.21 nsecs per iteration (total: 29,121,721, iterations: 100)
     828,960.60 CPU cycles per iteration, 2,85 GHz (total: 82,896,061, iterations: 100)
     2,225,414.35 instructions per iteration, 2,685 instr/cycle (total: 222,541,436, iterations: 100)
     471,001.08 branch instructions per iteration, 1,62 G/sec (total: 47,100,108, iterations: 100)
PASS   : UDSEntryBenchmark::loadSmallEntries()
RESULT : UDSEntryBenchmark::loadSmallEntries():
     874,512.19 nsecs per iteration (total: 87,451,220, iterations: 100)
     2,944,183.77 CPU cycles per iteration, 3,37 GHz (total: 294,418,378, iterations: 100)
     8,356,329.86 instructions per iteration, 2,838 instr/cycle (total: 835,632,986, iterations: 100)
     1,696,220.33 branch instructions per iteration, 1,94 G/sec (total: 169,622,033, iterations: 100)
PASS   : UDSEntryBenchmark::loadLargeEntries()
RESULT : UDSEntryBenchmark::loadLargeEntries():
     523,188.95 nsecs per iteration (total: 52,318,895, iterations: 100)
     1,802,280.59 CPU cycles per iteration, 3,44 GHz (total: 180,228,059, iterations: 100)
     4,638,708.87 instructions per iteration, 2,574 instr/cycle (total: 463,870,887, iterations: 100)
     966,316.32 branch instructions per iteration, 1,85 G/sec (total: 96,631,633, iterations: 100)
PASS   : UDSEntryBenchmark::cleanupTestCase()
Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 1717ms
********* Finished testing of UDSEntryBenchmark *********

```
</details>

Before:

<details><summary>5900X</summary>

```
ninja && ./bin/udsentry_benchmark -perf -iterations 100
[0/2] Re-checking globbed directories...
[2/2] Generating mo...
********* Start testing of UDSEntryBenchmark *********
Config: Using QtTest library 6.6.3, Qt 6.6.3 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 13.2.0), ubuntu 23.10
PASS   : UDSEntryBenchmark::initTestCase()
PASS   : UDSEntryBenchmark::createSmallEntries()
RESULT : UDSEntryBenchmark::createSmallEntries():
     1,193,799.26 nsecs per iteration (total: 119,379,926, iterations: 100)
     5,102,949.70 CPU cycles per iteration, 4,27 GHz (total: 510,294,971, iterations: 100)
     10,040,838.97 instructions per iteration, 1,968 instr/cycle (total: 1,004,083,897, iterations: 100)
     1,607,006.51 branch instructions per iteration, 1,35 G/sec (total: 160,700,651, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntries()
RESULT : UDSEntryBenchmark::createLargeEntries():
     1,047,783.89 nsecs per iteration (total: 104,778,389, iterations: 100)
     4,479,149.37 CPU cycles per iteration, 4,27 GHz (total: 447,914,937, iterations: 100)
     10,339,603.36 instructions per iteration, 2,308 instr/cycle (total: 1,033,960,337, iterations: 100)
     1,636,707.39 branch instructions per iteration, 1,56 G/sec (total: 163,670,740, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntriesOrderedInsert()
RESULT : UDSEntryBenchmark::createLargeEntriesOrderedInsert():
     1,078,283.10 nsecs per iteration (total: 107,828,310, iterations: 100)
     4,609,502.82 CPU cycles per iteration, 4,27 GHz (total: 460,950,282, iterations: 100)
     10,339,785.68 instructions per iteration, 2,243 instr/cycle (total: 1,033,978,569, iterations: 100)
     1,636,722.47 branch instructions per iteration, 1,52 G/sec (total: 163,672,248, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromSmallEntries()
RESULT : UDSEntryBenchmark::readFieldsFromSmallEntries():
     71,734,443.40 nsecs per iteration (total: 7,173,444,340, iterations: 100)
     306,658,822.22 CPU cycles per iteration, 4,27 GHz (total: 30,665,882,222, iterations: 100)
     651,080,571.29 instructions per iteration, 2,123 instr/cycle (total: 65,108,057,130, iterations: 100)
     107,599,038.31 branch instructions per iteration, 1,5 G/sec (total: 10,759,903,831, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromLargeEntries()
RESULT : UDSEntryBenchmark::readFieldsFromLargeEntries():
     928,909.26 nsecs per iteration (total: 92,890,926, iterations: 100)
     3,970,980.41 CPU cycles per iteration, 4,27 GHz (total: 397,098,042, iterations: 100)
     10,011,983.00 instructions per iteration, 2,521 instr/cycle (total: 1,001,198,301, iterations: 100)
     1,618,245.37 branch instructions per iteration, 1,74 G/sec (total: 161,824,538, iterations: 100)
PASS   : UDSEntryBenchmark::saveSmallEntries()
RESULT : UDSEntryBenchmark::saveSmallEntries():
     391,606.58 nsecs per iteration (total: 39,160,658, iterations: 100)
     1,673,987.23 CPU cycles per iteration, 4,27 GHz (total: 167,398,724, iterations: 100)
     5,492,048.00 instructions per iteration, 3,281 instr/cycle (total: 549,204,801, iterations: 100)
     1,140,941.55 branch instructions per iteration, 2,91 G/sec (total: 114,094,155, iterations: 100)
PASS   : UDSEntryBenchmark::saveLargeEntries()
RESULT : UDSEntryBenchmark::saveLargeEntries():
     162,880.67 nsecs per iteration (total: 16,288,068, iterations: 100)
     696,197.93 CPU cycles per iteration, 4,27 GHz (total: 69,619,794, iterations: 100)
     2,594,184.04 instructions per iteration, 3,726 instr/cycle (total: 259,418,404, iterations: 100)
     545,720.23 branch instructions per iteration, 3,35 G/sec (total: 54,572,024, iterations: 100)
PASS   : UDSEntryBenchmark::loadSmallEntries()
RESULT : UDSEntryBenchmark::loadSmallEntries():
     1,847,275.45 nsecs per iteration (total: 184,727,546, iterations: 100)
     7,896,793.91 CPU cycles per iteration, 4,27 GHz (total: 789,679,392, iterations: 100)
     18,671,291.28 instructions per iteration, 2,364 instr/cycle (total: 1,867,129,128, iterations: 100)
     3,328,595.33 branch instructions per iteration, 1,8 G/sec (total: 332,859,533, iterations: 100)
PASS   : UDSEntryBenchmark::loadLargeEntries()
RESULT : UDSEntryBenchmark::loadLargeEntries():
     1,562,225.62 nsecs per iteration (total: 156,222,563, iterations: 100)
     6,678,393.91 CPU cycles per iteration, 4,27 GHz (total: 667,839,391, iterations: 100)
     16,080,917.90 instructions per iteration, 2,408 instr/cycle (total: 1,608,091,790, iterations: 100)
     2,810,766.89 branch instructions per iteration, 1,8 G/sec (total: 281,076,689, iterations: 100)
PASS   : UDSEntryBenchmark::cleanupTestCase()
Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 10237ms
********* Finished testing of UDSEntryBenchmark *********

```

</details>

<details><summary>Laptop i7-8550U</summary>

```
 ninja && ./bin/udsentry_benchmark -perf -iterations 100

[0/2] Re-checking globbed directories...
[2/2] Generating mo...
********* Start testing of UDSEntryBenchmark *********
Config: Using QtTest library 6.7.0, Qt 6.7.0 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 13.2.1 20230801), arch unknown
PASS   : UDSEntryBenchmark::initTestCase()
PASS   : UDSEntryBenchmark::createSmallEntries()
RESULT : UDSEntryBenchmark::createSmallEntries():
     183,638.58 nsecs per iteration (total: 18,363,859, iterations: 100)
     528,858.76 CPU cycles per iteration, 2,88 GHz (total: 52,885,876, iterations: 100)
     1,104,844.93 instructions per iteration, 2,089 instr/cycle (total: 110,484,494, iterations: 100)
     212,139.39 branch instructions per iteration, 1,16 G/sec (total: 21,213,940, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntries()
RESULT : UDSEntryBenchmark::createLargeEntries():
     81,317.89 nsecs per iteration (total: 8,131,790, iterations: 100)
     229,731.54 CPU cycles per iteration, 2,83 GHz (total: 22,973,154, iterations: 100)
     291,120.51 instructions per iteration, 1,267 instr/cycle (total: 29,112,051, iterations: 100)
     53,566.62 branch instructions per iteration, 659 M/sec (total: 5,356,663, iterations: 100)
PASS   : UDSEntryBenchmark::createLargeEntriesOrderedInsert()
RESULT : UDSEntryBenchmark::createLargeEntriesOrderedInsert():
     953,192.30 nsecs per iteration (total: 95,319,230, iterations: 100)
     2,724,595.39 CPU cycles per iteration, 2,86 GHz (total: 272,459,539, iterations: 100)
     989,756.75 instructions per iteration, 0,363 instr/cycle (total: 98,975,675, iterations: 100)
     188,525.51 branch instructions per iteration, 198 M/sec (total: 18,852,551, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromSmallEntries()
RESULT : UDSEntryBenchmark::readFieldsFromSmallEntries():
     10,193,867.15 nsecs per iteration (total: 1,019,386,715, iterations: 100)
     31,687,357.92 CPU cycles per iteration, 3,11 GHz (total: 3,168,735,792, iterations: 100)
     71,854,155.25 instructions per iteration, 2,268 instr/cycle (total: 7,185,415,525, iterations: 100)
     19,273,659.62 branch instructions per iteration, 1,89 G/sec (total: 1,927,365,962, iterations: 100)
PASS   : UDSEntryBenchmark::readFieldsFromLargeEntries()
RESULT : UDSEntryBenchmark::readFieldsFromLargeEntries():
     220,510.70 nsecs per iteration (total: 22,051,070, iterations: 100)
     705,320.81 CPU cycles per iteration, 3,2 GHz (total: 70,532,081, iterations: 100)
     1,199,381.87 instructions per iteration, 1,700 instr/cycle (total: 119,938,188, iterations: 100)
     328,167.23 branch instructions per iteration, 1,49 G/sec (total: 32,816,724, iterations: 100)
PASS   : UDSEntryBenchmark::saveSmallEntries()
RESULT : UDSEntryBenchmark::saveSmallEntries():
     477,174.66 nsecs per iteration (total: 47,717,467, iterations: 100)
     1,476,174.48 CPU cycles per iteration, 3,09 GHz (total: 147,617,449, iterations: 100)
     4,337,759.12 instructions per iteration, 2,939 instr/cycle (total: 433,775,912, iterations: 100)
     917,090.82 branch instructions per iteration, 1,92 G/sec (total: 91,709,083, iterations: 100)
PASS   : UDSEntryBenchmark::saveLargeEntries()
RESULT : UDSEntryBenchmark::saveLargeEntries():
     253,218.85 nsecs per iteration (total: 25,321,886, iterations: 100)
     756,541.94 CPU cycles per iteration, 2,99 GHz (total: 75,654,195, iterations: 100)
     2,226,594.93 instructions per iteration, 2,943 instr/cycle (total: 222,659,494, iterations: 100)
     472,052.79 branch instructions per iteration, 1,86 G/sec (total: 47,205,280, iterations: 100)
PASS   : UDSEntryBenchmark::loadSmallEntries()
RESULT : UDSEntryBenchmark::loadSmallEntries():
     822,340.18 nsecs per iteration (total: 82,234,018, iterations: 100)
     2,763,434.85 CPU cycles per iteration, 3,36 GHz (total: 276,343,485, iterations: 100)
     8,075,257.50 instructions per iteration, 2,922 instr/cycle (total: 807,525,751, iterations: 100)
     1,609,203.72 branch instructions per iteration, 1,96 G/sec (total: 160,920,373, iterations: 100)
PASS   : UDSEntryBenchmark::loadLargeEntries()
RESULT : UDSEntryBenchmark::loadLargeEntries():
     571,344.43 nsecs per iteration (total: 57,134,443, iterations: 100)
     1,927,061.64 CPU cycles per iteration, 3,37 GHz (total: 192,706,165, iterations: 100)
     4,791,910.76 instructions per iteration, 2,487 instr/cycle (total: 479,191,077, iterations: 100)
     988,709.07 branch instructions per iteration, 1,73 G/sec (total: 98,870,908, iterations: 100)
PASS   : UDSEntryBenchmark::cleanupTestCase()
Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 2043ms
********* Finished testing of UDSEntryBenchmark *********

```
</details>

What the benchmark shows, is the typical UDSEntry creation is about the same. For big UDSentry, it is a bit worse except if the keys/values are inserted in type order then the new UDSEntry is faster.

Read access is always better, thanks to smaller vector to go through.

Saving is better for small entries and slightly worse for big entries due to keeping the same format when we could optimize it for the new data structure.
Loading smallEntries is slightly worse due to the format not being optimized not compensating for the overhead of having two vectors to fill.
The loadLargeEntries is better because the save inserts first strings then numbers, the load then can leverage better CPU cache access and compensate for the overhead.

The benchmark were not run using setting QT_NO_DEBUG, meaning the asserts were used.

Overall I think that's a good trade-off and its leaves small optimization on the table for KF7.

M  +75   -4    autotests/udsentry_benchmark.cpp
M  +2    -1    src/core/kfileitem.cpp
M  +5    -4    src/core/slavebase.cpp
M  +170  -51   src/core/udsentry.cpp
M  +87   -37   src/core/udsentry.h
M  +16   -12   src/kioworkers/file/file_unix.cpp
M  +14   -10   src/kioworkers/ftp/ftp.cpp
M  +26   -21   src/kioworkers/remote/remoteimpl.cpp
M  +13   -12   src/kioworkers/trash/kio_trash.cpp
M  +1    -1    src/kioworkers/trash/trashimpl.cpp
M  +9    -7    tests/messageboxworker/messageboxworker.cpp

https://invent.kde.org/frameworks/kio/-/commit/a7b7e249eded900d92aa2240793b40d7e1a65b8d

diff --git a/autotests/udsentry_benchmark.cpp b/autotests/udsentry_benchmark.cpp
index 259f35b11a..4f78887232 100644
--- a/autotests/udsentry_benchmark.cpp
+++ b/autotests/udsentry_benchmark.cpp
@@ -46,6 +46,7 @@ public:
 private Q_SLOTS:
     void createSmallEntries();
     void createLargeEntries();
+    void createLargeEntriesOrderedInsert();
     void readFieldsFromSmallEntries();
     void readFieldsFromLargeEntries();
     void saveSmallEntries();
@@ -114,14 +115,17 @@ void UDSEntryBenchmark::createSmallEntries()
     QBENCHMARK_ONCE {
         for (int i = 0; i < numberOfSmallUDSEntries; ++i) {
             KIO::UDSEntry entry;
-            entry.reserve(8);
+            entry.reserveStrings(3);
+            entry.reserveNumbers(5);
+
             entry.fastInsert(KIO::UDSEntry::UDS_NAME, names[i]);
+            entry.fastInsert(KIO::UDSEntry::UDS_USER, user);
+            entry.fastInsert(KIO::UDSEntry::UDS_GROUP, group);
+
             entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, i);
             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, i);
             entry.fastInsert(KIO::UDSEntry::UDS_SIZE, i);
             entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, i);
-            entry.fastInsert(KIO::UDSEntry::UDS_USER, user);
-            entry.fastInsert(KIO::UDSEntry::UDS_GROUP, group);
             entry.fastInsert(KIO::UDSEntry::UDS_ACCESS_TIME, i);
             m_smallEntries.append(entry);
         }
@@ -140,10 +144,23 @@ void UDSEntryBenchmark::createLargeEntries()
         names[i] = QString::number(i);
     }
 
+    int stringEntries = 0;
+    int numberEntries = 0;
+
+    for (int i = 0; i < numberOfLargeUDSEntries; ++i) {
+        for (uint field : std::as_const(m_fieldsForLargeEntries)) {
+            if (field & KIO::UDSEntry::UDS_STRING) {
+                stringEntries += 1;
+            } else {
+                numberEntries += 1;
+            }
+        }
+    }
     QBENCHMARK_ONCE {
         for (int i = 0; i < numberOfLargeUDSEntries; ++i) {
             KIO::UDSEntry entry;
-            entry.reserve(m_fieldsForLargeEntries.count());
+            entry.reserveStrings(stringEntries);
+            entry.reserveNumbers(numberEntries);
             for (uint field : std::as_const(m_fieldsForLargeEntries)) {
                 if (field & KIO::UDSEntry::UDS_STRING) {
                     entry.fastInsert(field, names[i]);
@@ -158,6 +175,60 @@ void UDSEntryBenchmark::createLargeEntries()
     Q_ASSERT(m_largeEntries.count() == numberOfLargeUDSEntries);
 }
 
+void UDSEntryBenchmark::createLargeEntriesOrderedInsert()
+{
+    m_largeEntries.clear();
+    m_largeEntries.reserve(numberOfLargeUDSEntries);
+
+    QList<QString> names(numberOfLargeUDSEntries);
+    for (int i = 0; i < numberOfLargeUDSEntries; ++i) {
+        names[i] = QString::number(i);
+    }
+
+    int stringEntries = 0;
+    int numberEntries = 0;
+
+    auto fields = QList{m_fieldsForLargeEntries};
+
+    for (int i = 0; i < numberOfLargeUDSEntries; ++i) {
+        for (uint field : std::as_const(fields)) {
+            if (field & KIO::UDSEntry::UDS_STRING) {
+                stringEntries += 1;
+            } else {
+                numberEntries += 1;
+            }
+        }
+    }
+
+    // orders the field to be in best case scenario for UDSEntry
+    std::ranges::sort(fields, [](uint a, uint b) {
+        bool isAString = (a & KIO::UDSEntry::UDS_STRING);
+        bool isBString = (b & KIO::UDSEntry::UDS_STRING);
+        if (isAString == isBString) {
+            return a < b;
+        }
+        return isAString;
+    });
+
+    QBENCHMARK_ONCE {
+        for (int i = 0; i < numberOfLargeUDSEntries; ++i) {
+            KIO::UDSEntry entry;
+            entry.reserveStrings(stringEntries);
+            entry.reserveNumbers(numberEntries);
+            for (uint field : std::as_const(fields)) {
+                if (field & KIO::UDSEntry::UDS_STRING) {
+                    entry.fastInsert(field, names[i]);
+                } else {
+                    entry.fastInsert(field, i);
+                }
+            }
+            m_largeEntries.append(entry);
+        }
+    }
+
+    Q_ASSERT(m_largeEntries.count() == numberOfLargeUDSEntries);
+}
+
 void UDSEntryBenchmark::readFieldsFromSmallEntries()
 {
     // Create the entries if they do not exist yet.
diff --git a/src/core/kfileitem.cpp b/src/core/kfileitem.cpp
index 468d18e665..7b12d58821 100644
--- a/src/core/kfileitem.cpp
+++ b/src/core/kfileitem.cpp
@@ -249,7 +249,8 @@ void KFileItemPrivate::init() const
         const QString path = m_url.adjusted(QUrl::StripTrailingSlash).path();
         const QByteArray pathBA = QFile::encodeName(path);
         if (LSTAT(pathBA.constData(), &buff, KIO::StatDefaultDetails) == 0) {
-            m_entry.reserve(9);
+            m_entry.reserveStrings(2);
+            m_entry.reserveNumbers(10);
             m_entry.replace(KIO::UDSEntry::UDS_DEVICE_ID, stat_dev(buff));
             m_entry.replace(KIO::UDSEntry::UDS_INODE, stat_ino(buff));
 
diff --git a/src/core/slavebase.cpp b/src/core/slavebase.cpp
index 70617ab9bf..f88cf90f6d 100644
--- a/src/core/slavebase.cpp
+++ b/src/core/slavebase.cpp
@@ -522,11 +522,12 @@ void SlaveBase::finished()
         if (!d->m_rootEntryListed) {
             qCWarning(KIO_CORE) << "UDSEntry for '.' not found, creating a default one. Please fix the" << QCoreApplication::applicationName() << "KIO worker.";
             KIO::UDSEntry entry;
-            entry.reserve(4);
             entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
-            entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
-            entry.fastInsert(KIO::UDSEntry::UDS_SIZE, 0);
-            entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH);
+            entry.insert({
+                {KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR},
+                {KIO::UDSEntry::UDS_SIZE, 0},
+                {KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IWGRP | S_IXGRP | S_IROTH | S_IXOTH},
+            });
             d->pendingListEntries.append(entry);
         }
 
diff --git a/src/core/udsentry.cpp b/src/core/udsentry.cpp
index 7e044e1566..1054756487 100644
--- a/src/core/udsentry.cpp
+++ b/src/core/udsentry.cpp
@@ -26,12 +26,18 @@ using namespace KIO;
 class KIO::UDSEntryPrivate : public QSharedData
 {
 public:
-    void reserve(int size);
+    void reserveNumbers(int size);
+    void reserveStrings(int size);
+    void reserve(std::initializer_list<uint> fields);
+    void insert(std::initializer_list<std::pair<uint, long long>> fields);
     void insert(uint udsField, const QString &value);
     void replace(uint udsField, const QString &value);
+    void insert(std::initializer_list<std::pair<uint, const QString &>> fields);
     void insert(uint udsField, long long value);
     void replace(uint udsField, long long value);
     int count() const;
+    int numbersCount() const;
+    int stringsCount() const;
     QString stringValue(uint udsField) const;
     long long numberValue(uint udsField, long long defaultValue = -1) const;
     QList<uint> fields() const;
@@ -47,94 +53,142 @@ public:
     static QString nameOfUdsField(uint field);
 
 private:
-    struct Field {
-        inline Field()
-        {
-        }
-        inline Field(const uint index, const QString &value) noexcept
+    struct StringField {
+        inline StringField(const uint index, const QString &value)
             : m_str(value)
             , m_index(index)
         {
         }
-        inline Field(const uint index, long long value = 0) noexcept
+
+        QString m_str;
+        uint m_index = 0;
+    };
+
+    struct NumberField {
+        inline NumberField(const uint index, long long value = 0)
             : m_long(value)
             , m_index(index)
         {
         }
 
-        QString m_str;
         long long m_long = LLONG_MIN;
         uint m_index = 0;
     };
-    std::vector<Field> storage;
+
+    std::vector<StringField> stringStorage;
+    std::vector<NumberField> numberStorage;
 };
 
-void UDSEntryPrivate::reserve(int size)
+void UDSEntryPrivate::reserve(std::initializer_list<uint> fields)
 {
-    storage.reserve(size);
+    int stringSize = 0;
+    int numberSize = 0;
+    for (const auto f : fields) {
+        if (f & UDSEntry::UDS_NUMBER) {
+            numberSize += 1;
+        } else {
+            stringSize += 1;
+        }
+    }
+    reserveStrings(stringSize);
+    reserveNumbers(numberSize);
+}
+
+void UDSEntryPrivate::reserveStrings(int size)
+{
+    stringStorage.reserve(size);
+}
+
+void UDSEntryPrivate::reserveNumbers(int size)
+{
+    numberStorage.reserve(size);
+}
+
+void UDSEntryPrivate::insert(std::initializer_list<std::pair<uint, const QString &>> fieldValuePairs)
+{
+    stringStorage.reserve(fieldValuePairs.size() + stringStorage.size());
+    for (const auto &f : fieldValuePairs) {
+        insert(f.first, f.second);
+    }
 }
 
 void UDSEntryPrivate::insert(uint udsField, const QString &value)
 {
     Q_ASSERT(udsField & KIO::UDSEntry::UDS_STRING);
-    Q_ASSERT(std::find_if(storage.cbegin(),
-                          storage.cend(),
-                          [udsField](const Field &entry) {
+    Q_ASSERT(std::find_if(stringStorage.cbegin(),
+                          stringStorage.cend(),
+                          [udsField](const StringField &entry) {
                               return entry.m_index == udsField;
                           })
-             == storage.cend());
-    storage.emplace_back(udsField, value);
+             == stringStorage.cend());
+    stringStorage.emplace_back(udsField, value);
 }
 
 void UDSEntryPrivate::replace(uint udsField, const QString &value)
 {
     Q_ASSERT(udsField & KIO::UDSEntry::UDS_STRING);
-    auto it = std::find_if(storage.begin(), storage.end(), [udsField](const Field &entry) {
+    auto it = std::find_if(stringStorage.begin(), stringStorage.end(), [udsField](const StringField &entry) {
         return entry.m_index == udsField;
     });
-    if (it != storage.end()) {
+    if (it != stringStorage.end()) {
         it->m_str = value;
         return;
     }
-    storage.emplace_back(udsField, value);
+    stringStorage.emplace(it, udsField, value);
+}
+
+void UDSEntryPrivate::insert(std::initializer_list<std::pair<uint, long long>> fieldValuePairs)
+{
+    numberStorage.reserve(fieldValuePairs.size() + numberStorage.size());
+    for (const auto &f : fieldValuePairs) {
+        insert(f.first, f.second);
+    }
 }
 
 void UDSEntryPrivate::insert(uint udsField, long long value)
 {
     Q_ASSERT(udsField & KIO::UDSEntry::UDS_NUMBER);
-    Q_ASSERT(std::find_if(storage.cbegin(),
-                          storage.cend(),
-                          [udsField](const Field &entry) {
+    Q_ASSERT(std::find_if(numberStorage.cbegin(),
+                          numberStorage.cend(),
+                          [udsField](const NumberField &entry) {
                               return entry.m_index == udsField;
                           })
-             == storage.cend());
-    storage.emplace_back(udsField, value);
+             == numberStorage.cend());
+    numberStorage.emplace_back(udsField, value);
 }
 
 void UDSEntryPrivate::replace(uint udsField, long long value)
 {
     Q_ASSERT(udsField & KIO::UDSEntry::UDS_NUMBER);
-    auto it = std::find_if(storage.begin(), storage.end(), [udsField](const Field &entry) {
+    auto it = std::find_if(numberStorage.begin(), numberStorage.end(), [udsField](const NumberField &entry) {
         return entry.m_index == udsField;
     });
-    if (it != storage.end()) {
+    if (it != numberStorage.end()) {
         it->m_long = value;
         return;
     }
-    storage.emplace_back(udsField, value);
+    numberStorage.emplace(it, udsField, value);
 }
 
 int UDSEntryPrivate::count() const
 {
-    return int(storage.size());
+    return stringStorage.size() + numberStorage.size();
+}
+int UDSEntryPrivate::numbersCount() const
+{
+    return numberStorage.size();
+}
+int UDSEntryPrivate::stringsCount() const
+{
+    return stringStorage.size();
 }
 
 QString UDSEntryPrivate::stringValue(uint udsField) const
 {
-    auto it = std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {
+    auto it = std::find_if(stringStorage.cbegin(), stringStorage.cend(), [udsField](const StringField &entry) {
         return entry.m_index == udsField;
     });
-    if (it != storage.cend()) {
+    if (it != stringStorage.cend()) {
         return it->m_str;
     }
     return QString();
@@ -142,10 +196,10 @@ QString UDSEntryPrivate::stringValue(uint udsField) const
 
 long long UDSEntryPrivate::numberValue(uint udsField, long long defaultValue) const
 {
-    auto it = std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {
+    auto it = std::find_if(numberStorage.cbegin(), numberStorage.cend(), [udsField](const NumberField &entry) {
         return entry.m_index == udsField;
     });
-    if (it != storage.cend()) {
+    if (it != numberStorage.cend()) {
         return it->m_long;
     }
     return defaultValue;
@@ -154,8 +208,11 @@ long long UDSEntryPrivate::numberValue(uint udsField, long long defaultValue) co
 QList<uint> UDSEntryPrivate::fields() const
 {
     QList<uint> res;
-    res.reserve(storage.size());
-    for (const Field &field : storage) {
+    res.reserve(stringStorage.size() + numberStorage.size());
+    for (const StringField &field : stringStorage) {
+        res.append(field.m_index);
+    }
+    for (const NumberField &field : numberStorage) {
         res.append(field.m_index);
     }
     return res;
@@ -163,28 +220,46 @@ QList<uint> UDSEntryPrivate::fields() const
 
 bool UDSEntryPrivate::contains(uint udsField) const
 {
-    auto it = std::find_if(storage.cbegin(), storage.cend(), [udsField](const Field &entry) {
-        return entry.m_index == udsField;
-    });
-    return (it != storage.cend());
+    if (udsField & KIO::UDSEntry::UDS_NUMBER) {
+        auto it = std::find_if(numberStorage.cbegin(), numberStorage.cend(), [udsField](const NumberField &entry) {
+            return entry.m_index == udsField;
+        });
+        return (it != numberStorage.cend());
+
+    } else {
+        auto it = std::find_if(stringStorage.cbegin(), stringStorage.cend(), [udsField](const StringField &entry) {
+            return entry.m_index == udsField;
+        });
+        return (it != stringStorage.cend());
+    }
 }
 
 void UDSEntryPrivate::clear()
 {
-    storage.clear();
+    stringStorage.clear();
+    numberStorage.clear();
 }
 
 void UDSEntryPrivate::save(QDataStream &s) const
 {
-    s << static_cast<quint32>(storage.size());
+    s << static_cast<quint32>(stringStorage.size() + numberStorage.size());
 
-    for (const Field &field : storage) {
+    for (const StringField &field : stringStorage) {
         uint uds = field.m_index;
         s << uds;
 
-        if (uds & KIO::UDSEntry::UDS_STRING) {
+        if (uds & KIO::UDSEntry::UDS_STRING) [[likely]] {
             s << field.m_str;
-        } else if (uds & KIO::UDSEntry::UDS_NUMBER) {
+        } else {
+            Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
+        }
+    }
+
+    for (const NumberField &field : numberStorage) {
+        uint uds = field.m_index;
+        s << uds;
+
+        if (uds & KIO::UDSEntry::UDS_NUMBER) [[likely]] {
             s << field.m_long;
         } else {
             Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
@@ -198,7 +273,8 @@ void UDSEntryPrivate::load(QDataStream &s)
 
     quint32 size;
     s >> size;
-    reserve(size);
+    reserveStrings(size / 3);
+    reserveNumbers(size * 2 / 3);
 
     // We cache the loaded strings. Some of them, like, e.g., the user,
     // will often be the same for many entries in a row. Caching them
@@ -236,7 +312,7 @@ void UDSEntryPrivate::load(QDataStream &s)
             s >> value;
             insert(uds, value);
         } else {
-            Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
+            Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an unexpected type");
         }
     }
 }
@@ -327,11 +403,17 @@ void UDSEntryPrivate::debugUDSEntry(QDebug &stream) const
 {
     QDebugStateSaver saver(stream);
     stream.nospace() << "[";
-    for (const Field &field : storage) {
+    for (const StringField &field : stringStorage) {
         stream << " " << nameOfUdsField(field.m_index) << "=";
         if (field.m_index & KIO::UDSEntry::UDS_STRING) {
             stream << field.m_str;
-        } else if (field.m_index & KIO::UDSEntry::UDS_NUMBER) {
+        } else {
+            Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
+        }
+    }
+    for (const NumberField &field : numberStorage) {
+        stream << " " << nameOfUdsField(field.m_index) << "=";
+        if (field.m_index & KIO::UDSEntry::UDS_NUMBER) {
             stream << field.m_long;
         } else {
             Q_ASSERT_X(false, "KIO::UDSEntry", "Found a field with an invalid type");
@@ -354,10 +436,11 @@ UDSEntry::UDSEntry(const QT_STATBUF &buff, const QString &name)
     : d(new UDSEntryPrivate())
 {
 #ifndef Q_OS_WIN
-    d->reserve(12);
+    d->reserveNumbers(11);
 #else
-    d->reserve(8);
+    d->reserveNumbers(9);
 #endif
+    d->reserveStrings(1);
     d->insert(UDS_NAME, name);
     d->insert(UDS_SIZE, buff.st_size);
     d->insert(UDS_DEVICE_ID, buff.st_dev);
@@ -400,9 +483,14 @@ bool UDSEntry::isLink() const
     return !stringValue(UDS_LINK_DEST).isEmpty();
 }
 
-void UDSEntry::reserve(int size)
+void KIO::UDSEntry::reserveStrings(int size)
 {
-    d->reserve(size);
+    d->reserveStrings(size);
+}
+
+void KIO::UDSEntry::reserveNumbers(int size)
+{
+    d->reserveNumbers(size);
 }
 
 void UDSEntry::fastInsert(uint field, const QString &value)
@@ -435,6 +523,16 @@ int UDSEntry::count() const
     return d->count();
 }
 
+int KIO::UDSEntry::stringsCount() const
+{
+    return d->stringsCount();
+}
+
+int KIO::UDSEntry::numbersCount() const
+{
+    return d->numbersCount();
+}
+
 bool UDSEntry::contains(uint field) const
 {
     return d->contains(field);
@@ -444,6 +542,27 @@ void UDSEntry::clear()
 {
     d->clear();
 }
+
+void UDSEntry::reserve(int size)
+{
+    d->reserveStrings(size / 3);
+    d->reserveNumbers(size * 2 / 3);
+}
+
+void UDSEntry::reserve(std::initializer_list<uint> fields)
+{
+    d->reserve(fields);
+}
+
+void UDSEntry::insert(std::initializer_list<std::pair<uint, const QString &>> fieldValuePairs)
+{
+    d->insert(fieldValuePairs);
+}
+
+void UDSEntry::insert(std::initializer_list<std::pair<uint, long long>> fieldValuePairs)
+{
+    d->insert(fieldValuePairs);
+}
 // END UDSEntry
 
 KIOCORE_EXPORT QDebug operator<<(QDebug stream, const KIO::UDSEntry &entry)
diff --git a/src/core/udsentry.h b/src/core/udsentry.h
index 19c754f947..a0a30f04e3 100644
--- a/src/core/udsentry.h
+++ b/src/core/udsentry.h
@@ -3,6 +3,7 @@
     SPDX-FileCopyrightText: 2000-2005 David Faure <[email protected]>
     SPDX-FileCopyrightText: 2007 Norbert Frese <[email protected]>
     SPDX-FileCopyrightText: 2007 Thiago Macieira <[email protected]>
+    SPDX-FileCopyrightText: 2023 Méven Car <[email protected]>
 
     SPDX-License-Identifier: LGPL-2.0-only
 */
@@ -57,13 +58,11 @@ KIOCORE_EXPORT bool operator!=(const UDSEntry &entry, const UDSEntry &other);
  *
  * The KIO::listDir() and KIO:stat() operations use this data structure.
  *
- * KIO defines a number of standard fields, see the UDS_XXX enums (see StandardFieldTypes).
- * at the moment UDSEntry only provides fields with numeric indexes,
- * but there might be named fields with string indexes in the future.
+ * KIO defines a number of standard fields, see UDSEntry::StandardFieldTypes enum.
  *
- * For instance, to retrieve the name of the entry, use:
+ * For instance, to retrieve the display name of the entry, use:
  * \code
- * QString displayName = entry.stringValue( KIO::UDSEntry::UDS_NAME );
+ * QString displayName = entry.stringValue( KIO::UDSEntry::UDS_DISPLAY_NAME );
  * \endcode
  *
  * To know the modification time of the file/url in seconds since UNIX epoch, use:
@@ -139,37 +138,111 @@ public:
 
     /*!
      * Calling this function before inserting items into an empty UDSEntry may save time and memory.
-     *
      * \a size number of items for which memory will be pre-allocated
+     *
+     * \deprecated[6.29] Use reserveStrings() and reserveNumbers().
      */
     void reserve(int size);
 
     /*!
-     * insert field with string value, it will assert if the field is already inserted. In that case, use replace() instead.
+     * Calling those functions before inserting items into an empty UDSEntry may save time and memory.
+     * \a size number of items for which memory will be pre-allocated
      *
-     * \a field numeric field id
+     * Use reserveStrings for UDS_STRING fields and reserveNumbers for UDS_NUMBER fields.
      *
-     * \a value to set
+     * \since 6.29
+     */
+    void reserveStrings(int size);
+    //! \sa reserveStrings
+    void reserveNumbers(int size);
+
+    /*!
+     * Pre-allocate `fields` fields in the backend storage according to their UDS_TYPE
+     *
+     * Example:
+     *
+     *     UDSEntry entry;
+     *     entry.reserve({UDS_SIZE, UDS_ACCESS, UDS_MODIFICATION_TIME, UDS_NAME});
+     *
+     * \a fields
+     * \since 6.29
+     */
+    void reserve(std::initializer_list<uint> fields);
+
+    /*!
+     * Insert the values passed as pairs {field, value} in a initializer_list
+     *
+     * This will first pre-allocates the necessary memory in the underlying storage vector.
+     *
+     * Example:
+     *
+     *     UDSEntry entry;
+     *     entry.insert({{UDS_SIZE, 0}, {UDS_ACCESS, 0}});
+     *
+     * \a fields
+     * \since 6.29
+     */
+    void insert(std::initializer_list<std::pair<uint, const QString &>> fieldValuePairs);
+
+    /*!
+     * Insert the values passed as pairs {field, value} in a initializer_list
+     *
+     * This will first pre-allocates the necessary memory in the underlying storage vector.
+     *
+     * Example:
+     *
+     *     UDSEntry entry;
+     *     entry.insert({{UDS_NAME, ""}, {UDS_USER, ""}});
      *
+     * \a fields
+     * \since 6.29
+     */
+    void insert(std::initializer_list<std::pair<uint, long long>> fieldValuePairs);
+
+    /*!
+     * Insert field with string value, it will assert if the field is already inserted. In that case, use replace() instead.
+     * \a field numeric field id
+     * \a value to set
      * \since 5.48
      */
     void fastInsert(uint field, const QString &value);
 
     /*!
-     * insert field with numeric value, it will assert if the field is already inserted. In that case, use replace() instead.
-     *
+     * Insert field with numeric value, it will assert if the field is already inserted. In that case, use replace() instead.
      * \a field numeric field id
-     *
      * \a l value to set
-     *
      * \since 5.48
      */
     void fastInsert(uint field, long long l);
 
     /*!
-     * Returns the number of fields
+     * Replace or insert field with string value
+     * \a field numeric field id
+     * \a value to set
+     * \since 5.47
+     */
+    void replace(uint field, const QString &value);
+
+    /*!
+     * Replace or insert field with numeric value
+     * \a field numeric field id
+     * \a l value to set
+     * \since 5.47
+     */
+    void replace(uint field, long long l);
+
+    /*!
+     * The number of fields
      */
     int count() const;
+    /*!
+     * The number of number fields (including time fields)
+     */
+    int numbersCount() const;
+    /*!
+     * The number of string fields
+     */
+    int stringsCount() const;
 
     /*!
      * check existence of a field
@@ -311,29 +384,6 @@ private:
     friend KIOCORE_EXPORT QDataStream & ::operator<<(QDataStream &s, const KIO::UDSEntry &a);
     friend KIOCORE_EXPORT QDataStream & ::operator>>(QDataStream &s, KIO::UDSEntry &a);
     friend KIOCORE_EXPORT QDebug(::operator<<)(QDebug stream, const KIO::UDSEntry &entry);
-
-public:
-    /*!
-     * Replace or insert field with string value
-     *
-     * \a field numeric field id
-     *
-     * \a value to set
-     *
-     * \since 5.47
-     */
-    void replace(uint field, const QString &value);
-
-    /*!
-     * Replace or insert field with numeric value
-     *
-     * \a field numeric field id
-     *
-     * \a l value to set
-     *
-     * \since 5.47
-     */
-    void replace(uint field, long long l);
 };
 
 // allows operator ^ and | between UDSEntry::StandardFieldTypes and UDSEntry::ItemTypes
diff --git a/src/kioworkers/file/file_unix.cpp b/src/kioworkers/file/file_unix.cpp
index 16cb150a91..86b1daa00a 100644
--- a/src/kioworkers/file/file_unix.cpp
+++ b/src/kioworkers/file/file_unix.cpp
@@ -133,47 +133,51 @@ static QByteArray readlinkToBuffer(const StatStruct &buf, const QByteArray &path
 
 static bool createUDSEntry(const QString &filename, const QByteArray &path, UDSEntry &entry, KIO::StatDetails details, const QString &fullPath)
 {
-    assert(entry.count() == 0); // by contract :-)
-    int entries = 0;
+    assert(entry.count() == 0); // by contract :-)    assert(entry.count() == 0); // by contract :-)
+    int numberEntries = 0;
+    int stringEntries = 0;
     if (details & KIO::StatBasic) {
         // filename, access, type, size, linkdest
-        entries += 5;
+        numberEntries += 3;
+        stringEntries += 2;
     }
     if (details & KIO::StatUser) {
         // uid, gid
-        entries += 2;
+        numberEntries += 2;
     }
     if (details & KIO::StatTime) {
         // atime, mtime, btime
-        entries += 3;
+        numberEntries += 3;
         if ((details & KIO::StatTimeNsOffset) == KIO::StatTimeNsOffset) {
             // atime, mtime, btime ns offsets
-            entries += 3;
+            numberEntries += 3;
         }
     }
     if (details & KIO::StatAcl) {
         // acl data
-        entries += 3;
+        numberEntries += 1;
+        stringEntries += 2;
     }
     if (details & KIO::StatInode) {
         // dev, inode
-        entries += 2;
+        numberEntries += 2;
     }
     if (details & KIO::StatMimeType) {
         // mimetype
-        entries += 1;
+        stringEntries += 1;
     }
 #if HAVE_STATX_SUBVOL
     if (details & KIO::StatSubVolId) {
-        entries += 1;
+        numberEntries += 1;
     }
 #endif
 #if HAVE_STATX_MNT_ID_UNIQUE
     if (details & KIO::StatMountId) {
-        entries += 1;
+        numberEntries += 1;
     }
 #endif
-    entry.reserve(entries);
+    entry.reserveNumbers(numberEntries);
+    entry.reserveStrings(stringEntries);
 
     if (details & KIO::StatBasic) {
         entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename);
diff --git a/src/kioworkers/ftp/ftp.cpp b/src/kioworkers/ftp/ftp.cpp
index c32fcc9449..435a362cf5 100644
--- a/src/kioworkers/ftp/ftp.cpp
+++ b/src/kioworkers/ftp/ftp.cpp
@@ -1243,7 +1243,8 @@ void FtpInternal::ftpCreateUDSEntry(const QString &filename, const FtpEntry &ftp
 {
     Q_ASSERT(entry.count() == 0); // by contract :-)
 
-    entry.reserve(10);
+    entry.reserveStrings(5);
+    entry.reserveNumbers(5);
     entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename);
     entry.fastInsert(KIO::UDSEntry::UDS_SIZE, ftpEnt.size);
     entry.fastInsert(KIO::UDSEntry::UDS_MODIFICATION_TIME, ftpEnt.date.toSecsSinceEpoch());
@@ -1279,7 +1280,8 @@ void FtpInternal::ftpShortStatAnswer(const QString &filename, bool isDir)
 {
     UDSEntry entry;
 
-    entry.reserve(4);
+    entry.reserveStrings(isDir ? 2 : 1);
+    entry.reserveNumbers(2);
     entry.fastInsert(KIO::UDSEntry::UDS_NAME, filename);
     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, isDir ? S_IFDIR : S_IFREG);
     entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
@@ -1327,14 +1329,16 @@ Result FtpInternal::stat(const QUrl &url)
     // We can't stat root, but we know it's a dir.
     if (path.isEmpty() || path == QLatin1String("/")) {
         UDSEntry entry;
-        entry.reserve(6);
-        // entry.insert( KIO::UDSEntry::UDS_NAME, UDSField( QString() ) );
-        entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
-        entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
-        entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
-        entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH);
-        entry.fastInsert(KIO::UDSEntry::UDS_USER, QStringLiteral("root"));
-        entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QStringLiteral("root"));
+        entry.insert({
+            {KIO::UDSEntry::UDS_NAME, QStringLiteral(".")},
+            {KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")},
+            {KIO::UDSEntry::UDS_USER, QStringLiteral("root")},
+            {KIO::UDSEntry::UDS_GROUP, QStringLiteral("root")},
+        });
+        entry.insert({
+            {KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR},
+            {KIO::UDSEntry::UDS_ACCESS, S_IRUSR | S_IWUSR | S_IXUSR | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH},
+        });
         // no size
 
         q->statEntry(entry);
diff --git a/src/kioworkers/remote/remoteimpl.cpp b/src/kioworkers/remote/remoteimpl.cpp
index 45c98b430b..67827a06cd 100644
--- a/src/kioworkers/remote/remoteimpl.cpp
+++ b/src/kioworkers/remote/remoteimpl.cpp
@@ -93,15 +93,18 @@ QUrl RemoteImpl::findBaseURL(const QString &filename) const
 void RemoteImpl::createTopLevelEntry(KIO::UDSEntry &entry) const
 {
     entry.clear();
-    entry.reserve(8);
-    entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
-    entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Network"));
-    entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
-    entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0500);
-    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
-    entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("folder-remote"));
-    entry.fastInsert(KIO::UDSEntry::UDS_USER, QStringLiteral("root"));
-    entry.fastInsert(KIO::UDSEntry::UDS_GROUP, QStringLiteral("root"));
+    entry.insert({
+        {KIO::UDSEntry::UDS_NAME, QStringLiteral(".")},
+        {KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Network")},
+        {KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")},
+        {KIO::UDSEntry::UDS_ICON_NAME, QStringLiteral("folder-remote")},
+        {KIO::UDSEntry::UDS_USER, QStringLiteral("root")},
+        {KIO::UDSEntry::UDS_GROUP, QStringLiteral("root")},
+    });
+    entry.insert({
+        {KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR},
+        {KIO::UDSEntry::UDS_ACCESS, 0500},
+    });
 }
 
 bool RemoteImpl::createEntry(KIO::UDSEntry &entry, const QString &directory, const QString &file) const
@@ -123,18 +126,20 @@ bool RemoteImpl::createEntry(KIO::UDSEntry &entry, const QString &directory, con
     QString new_filename = file;
     new_filename.chop(8);
 
-    entry.reserve(8);
-    entry.fastInsert(KIO::UDSEntry::UDS_NAME, desktop.readName());
-    entry.fastInsert(KIO::UDSEntry::UDS_URL, QLatin1String("remote:/") + new_filename);
-
-    entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
-    entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0500);
-    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
-
-    const QString icon = desktop.readIcon();
-    entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, icon);
-    entry.fastInsert(KIO::UDSEntry::UDS_LINK_DEST, desktop.readUrl());
-    entry.fastInsert(KIO::UDSEntry::UDS_TARGET_URL, desktop.readUrl());
+    const QString url = desktop.readUrl();
+    const QString remoteUrl = QLatin1String("remote:/") + new_filename;
+    entry.insert({
+        {KIO::UDSEntry::UDS_NAME, desktop.readName()},
+        {KIO::UDSEntry::UDS_URL, remoteUrl},
+        {KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")},
+        {KIO::UDSEntry::UDS_ICON_NAME, desktop.readIcon()},
+        {KIO::UDSEntry::UDS_LINK_DEST, url},
+        {KIO::UDSEntry::UDS_TARGET_URL, url},
+    });
+    entry.insert({
+        {KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR},
+        {KIO::UDSEntry::UDS_ACCESS, 0500},
+    });
     return true;
 }
 
diff --git a/src/kioworkers/trash/kio_trash.cpp b/src/kioworkers/trash/kio_trash.cpp
index e8267d42b2..85659926c5 100644
--- a/src/kioworkers/trash/kio_trash.cpp
+++ b/src/kioworkers/trash/kio_trash.cpp
@@ -290,17 +290,17 @@ KIO::WorkerResult TrashProtocol::copyOrMoveToTrash(const QUrl &src, const QUrl &
 
 void TrashProtocol::createTopLevelDirEntry(KIO::UDSEntry &entry)
 {
-    entry.reserve(entry.count() + 8);
-    entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
-    entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Trash"));
-    entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
-    entry.fastInsert(KIO::UDSEntry::UDS_ACCESS, 0700);
-    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
-    entry.fastInsert(KIO::UDSEntry::UDS_ICON_NAME, impl.isEmpty() ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full"));
-    entry.fastInsert(KIO::UDSEntry::UDS_USER, m_userName);
-    entry.fastInsert(KIO::UDSEntry::UDS_GROUP, m_groupName);
-    entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_USER_ID, m_userId);
-    entry.fastInsert(KIO::UDSEntry::UDS_LOCAL_GROUP_ID, m_groupId);
+    entry.insert({{KIO::UDSEntry::UDS_NAME, QStringLiteral(".")},
+                  {KIO::UDSEntry::UDS_DISPLAY_NAME, i18n("Trash")},
+                  {KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")},
+                  {KIO::UDSEntry::UDS_ICON_NAME, impl.isEmpty() ? QStringLiteral("user-trash") : QStringLiteral("user-trash-full")},
+                  {KIO::UDSEntry::UDS_USER, m_userName},
+                  {KIO::UDSEntry::UDS_GROUP, m_groupName}});
+
+    entry.insert({{KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR},
+                  {KIO::UDSEntry::UDS_ACCESS, 0700},
+                  {KIO::UDSEntry::UDS_LOCAL_USER_ID, m_userId},
+                  {KIO::UDSEntry::UDS_LOCAL_GROUP_ID, m_groupId}});
 }
 
 KIO::StatDetails TrashProtocol::getStatDetails()
@@ -457,7 +457,8 @@ bool TrashProtocol::createUDSEntry(const QString &physicalPath,
                                    KIO::UDSEntry &entry,
                                    const TrashedFileInfo &info)
 {
-    entry.reserve(16);
+    entry.reserveNumbers(7);
+    entry.reserveStrings(9);
     QByteArray physicalPath_c = QFile::encodeName(physicalPath);
     QT_STATBUF buff;
     if (QT_LSTAT(physicalPath_c.constData(), &buff) == -1) {
diff --git a/src/kioworkers/trash/trashimpl.cpp b/src/kioworkers/trash/trashimpl.cpp
index 2679cf6bc0..897b471045 100644
--- a/src/kioworkers/trash/trashimpl.cpp
+++ b/src/kioworkers/trash/trashimpl.cpp
@@ -1003,7 +1003,7 @@ KIO::UDSEntry TrashImpl::trashUDSEntry(KIO::StatDetails details)
             }
         }
 
-        entry.reserve(5);
+        entry.reserveNumbers(5);
         entry.fastInsert(KIO::UDSEntry::UDS_RECURSIVE_SIZE, static_cast<long long>(size));
 
         const auto modifiedDateNsOffset = (latestModifiedDate % 1000) * 1000000;
diff --git a/tests/messageboxworker/messageboxworker.cpp b/tests/messageboxworker/messageboxworker.cpp
index 59c7a144e9..da7dc79fc0 100644
--- a/tests/messageboxworker/messageboxworker.cpp
+++ b/tests/messageboxworker/messageboxworker.cpp
@@ -205,10 +205,11 @@ int messageBoxType(const QUrl &url)
 KIO::UDSEntry typeDirEntry(int messageBoxType)
 {
     KIO::UDSEntry entry;
-    entry.reserve(4);
-    entry.fastInsert(KIO::UDSEntry::UDS_NAME, QString::number(messageBoxType));
-    entry.fastInsert(KIO::UDSEntry::UDS_DISPLAY_NAME, typeNames.value(messageBoxType));
-    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
+    entry.insert({
+        {KIO::UDSEntry::UDS_NAME, QString::number(messageBoxType)},
+        {KIO::UDSEntry::UDS_DISPLAY_NAME, typeNames.value(messageBoxType)},
+        {KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")},
+    });
     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
     return entry;
 }
@@ -217,9 +218,10 @@ KIO::UDSEntry rootDirEntry()
 {
     KIO::UDSEntry entry;
 
-    entry.reserve(3);
-    entry.fastInsert(KIO::UDSEntry::UDS_NAME, QStringLiteral("."));
-    entry.fastInsert(KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory"));
+    entry.insert({
+        {KIO::UDSEntry::UDS_NAME, QStringLiteral(".")},
+        {KIO::UDSEntry::UDS_MIME_TYPE, QStringLiteral("inode/directory")},
+    });
     entry.fastInsert(KIO::UDSEntry::UDS_FILE_TYPE, S_IFDIR);
 
     return entry;
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.