At worst, wrapping a 25-row query result and rendering it costs about 2 microseconds (0.002 ms) more than the same page written by hand with plain arrays and manual HTML encoding; at best it comes out ahead, because SmartString often encodes faster than PHP's built-in encoder (2.5x on the long-text detail page below). Memory overhead is about 300 bytes per row no matter how large the fields are.
All times on this page are in milliseconds (ms), thousandths of a second. For scale: response-time research puts the threshold where people start to notice a delay at about 100 ms, and around 200 ms a page starts to feel slow. For comparison, even a fast database query costs over ten times more than wrapping its results, and a typical page's queries together cost hundreds of times more.
The rest of this page is the measurements behind those claims, and the one case where the overhead is worth thinking about.
Contents:
A news site with 25 records (60-char title, 300-char summary, 5KB content),
rendered two ways: SmartArrayHtml, and the same page written with plain
arrays and an htmlspecialchars() helper. Field text matches real-prose
character densities (shared with SmartString's benchmark corpus), and both
versions are verified to produce byte-identical HTML before timing:
| Scenario | Plain array | SmartArray | Difference |
|---|---|---|---|
| List page (25 rows, encoded title+summary) | 0.0271 ms | 0.0288 ms | +0.0017 ms (1.06x) |
| Detail page (1 row, encoded 5KB body) | 0.0120 ms | 0.0047 ms | 2.5x faster |
| Raw loop (plain SmartArray, create + 50 reads) | 0.0004 ms | 0.0070 ms | +0.0066 ms |
SmartArray times include constructing the object from the plain records
array, the same work the database layer does when it returns results. The
first two rows use SmartArrayHtml with encoding on. The raw loop has no
output or encoding at all: it uses plain SmartArray (no HTML mode),
where field reads return plain strings and no SmartString objects are
created, so data-processing code skips the encoding layer entirely.
Encoding is not where the time goes. Render the same pages with no encoding on either side (plain SmartArray against plain arrays echoed raw) and the list page still measures +0.0066 ms: the overhead is construction either way, and SmartString encodes as fast as or faster than hand-encoding, so HTML mode adds nothing on net.
To put the list-page row in perspective: to lose a single millisecond on one page load, your code would have to build and render that 25-row list about 600 times. Same math for the raw-loop row: wrapping one 25-row query result in a SmartArray and looping over it costs 0.0066 ms more than the plain array, so a single request would have to create 151 separate SmartArrays, 25 rows each, and loop over all of them - 3,775 rows - before the total penalty reached one millisecond.
Why the detail page is faster. SmartString checks whether text is
plain ASCII and, when it is, swaps the five special characters with
str_replace() instead of running htmlspecialchars()'s full UTF-8
scan. On multi-KB content fields that saves more than construction costs.
See SmartString's
performance page
for the encoding measurements across platforms.
| Data | Payload size | SmartArray adds | Per record |
|---|---|---|---|
| 25 news records | ~133 KB | +7.8 KB | ~320 bytes |
| 1,000 news records | ~5.2 MB | +294 KB | ~301 bytes |
Field values are never copied: PHP strings are reference-counted, so a 5KB content field is shared between the plain array and the SmartArray that wraps it. The per-record overhead is the row object itself, so the bytes per row are the constant, not a percentage: on these ~5 KB news records it works out to 6%, on records with a 50 KB body it would be 0.6%, and on lean three-column rows it could exceed 100% - while staying the same few hundred bytes each time.
Almost never. Every number above is a fraction of a fraction of the smallest delay a person can perceive. Write the code that's simplest to read and work with - for query results rendered as HTML, that's SmartArrayHtml - and don't spend a line of it dodging these costs. If a page is genuinely slow, benchmark it and fix what the benchmark points at: it will be a query, a missing index, or an API call, not the array wrapper.
The exception is when you're deliberately optimizing for milliseconds - a rendering budget of a few ms, or one request processing tens of thousands of rows. For that case, here is the cost breakdown. Construction is nearly all of it; everything after is close to free:
| Operation (25 rows, plain SmartArray) | Cost |
|---|---|
| Construct from plain records array | 0.0045 ms |
Construct via fromDatabaseRows() |
0.0037 ms |
| foreach over all rows | 0.0007 ms |
Read a field ($row->title) |
0.000046 ms |
toArray() on the record set |
0.0006 ms |
toArray() on one flat row |
0.000027 ms |
ZenDB constructs its result sets with fromDatabaseRows(), the faster
construct row above: database rows are uniform (same columns in every row,
plain scalar values), so it skips the checks the general constructor runs
on arbitrary input and comes out about 20% faster. The method is internal
(ZenDB plumbing, not a public API), so use new() in your own code.
Internals that keep those numbers small: all-scalar rows are built by
cloning a shared template and assigning their data in one copy-on-write
step; foreach uses a C-level ArrayIterator whenever no SmartString
wrapping can happen (raw mode, or record sets where every value is a row);
toArray() hands back internal data as-is when there are no child rows
to convert; and builtin calls like is_scalar() and count() are
imported with use function, so they compile to single opcodes instead
of runtime name lookups.
Two things follow from construction being the whole cost:
- It's eager, per row fetched. Query 500 rows to show 10 and you pay for 500. LIMIT in the query beats any amount of avoiding SmartArray.
- Hot loops: unwrap once. A report loop touching every field thousands
of times can call
->toArray()first (0.000027 ms on a flat row) and loop the plain array.
Every number on this page comes from one script, which builds the test data, verifies both versions produce byte-identical HTML, and then times them. Local runs are direction checks; numbers move a few percent between runs and more between machines. Run with opcache on and xdebug off:
php -n -d zend_extension=opcache -d opcache.enable_cli=1 benchmarks/news-page.php← Documentation Index | ← Prev: Troubleshooting | Next: AI Reference →