Skip to content

Commit fbf325a

Browse files
committed
Drop array-of-type functions (for now)
While these functions do, in themselves, provide a performance advantage over writing a plain array, this doesn't consider hidden costs. - When encoding a chunk palette, the palette has to first be converted from a native array to a PHP array just for this extension to accept and turn back into an std::vector. This makes no sense and is needlessly costing performance. - Encoding chunk palettes in batch also requires allocating a *second* array, just for this extension's consumption, because the values in the palette have to be transformed before encoding. We could modify the arrays in-place, but it's optimising the wrong problem. - Generally speaking, the places that actually benefit from these functions are either cold paths, or they'd benefit more generally from not using PHP arrays, or both. So it's hard to make the case for optimising this particular use case. - The cost of turning PHP arrays into std::vector in this extension (a necessary step) is rather high. The overarching theme here is that arrays need to be avoided everywhere where performance or memory usage is an issue, and having this extension accept and especially *return* them is hobbling consumers of the API. The logical next step would be to add classes like IntArray, LongArray etc to act as a thin PHP wrapper around native uint32_t[] etc, and then have array APIs in ext-encoding that would accept and return these specialised classes instead of PHP arrays. However, this is an endeavour I'd prefer to undertake speculatively *after* getting the lion's share of the benefits from ext-encoding's regular (non array) functions, given the rather limited benefit expected anyway (we barely have any use cases for writing direct array-of-type anyway). In the meantime, having these barely-useful functions in the API would give us backwards-compatibility constraints if they made it to a stable release, which would be an obstacle for implementing actually good array-of-type functions later. So, bye-bye array-of-type, for now...
1 parent dfd183d commit fbf325a

File tree

5 files changed

+19
-336
lines changed

5 files changed

+19
-336
lines changed

classes/Types.cpp

Lines changed: 2 additions & 177 deletions
Original file line numberDiff line numberDiff line change
@@ -30,22 +30,7 @@ extern "C" {
3030
ARG_INFOS(zend_long, IS_LONG)
3131
ARG_INFOS(double, IS_DOUBLE)
3232

33-
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_read_array, 0, 2, IS_ARRAY, 0)
34-
ZEND_ARG_OBJ_INFO(0, buffer, pmmp\\encoding\\ByteBufferReader, 0)
35-
ZEND_ARG_TYPE_INFO(0, count, IS_LONG, 0)
36-
ZEND_END_ARG_INFO()
37-
38-
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_write_array, 0, 2, IS_VOID, 0)
39-
ZEND_ARG_OBJ_INFO(0, buffer, pmmp\\encoding\\ByteBufferWriter, 0)
40-
ZEND_ARG_ARRAY_INFO(0, values, 0)
41-
ZEND_END_ARG_INFO()
42-
43-
4433
#if PHP_VERSION_ID >= 80400
45-
static const char* read_zend_long_array_doc_comment = "/**\n\t * @return int[]\n\t * @phpstan-return list<int>\n\t * @throws DataDecodeException\n\t */";
46-
static const char* read_double_array_doc_comment = "/**\n\t * @return float[]\n\t * @phpstan-return list<float>\n\t * @throws DataDecodeException\n\t */";
47-
static const char* write_zend_long_array_doc_comment = "/**\n\t * @param int[] $values\n\t * @phpstan-param list<int> $values\n\t */";
48-
static const char* write_double_array_doc_comment = "/**\n\t * @param float[] $values\n\t * @phpstan-param list<float> $values\n\t */";
4934
static const char* read_generic_doc_comment = "/** @throws DataDecodeException */";
5035
#endif
5136

@@ -63,27 +48,6 @@ static inline void assignZval(zval* zv, TValue value, std::type_identity<double>
6348
ZVAL_DOUBLE(zv, value);
6449
}
6550

66-
template<typename TValue, typename TZendValue>
67-
static inline void assignZvalArray(zval* zv, std::vector<TValue>& valueArray, std::type_identity<TZendValue> zendType) = delete;
68-
69-
template<typename TValue>
70-
static inline void assignZvalArray(zval* zv, std::vector<TValue>& valueArray, std::type_identity<zend_long> zendType) {
71-
array_init_size(zv, valueArray.size());
72-
73-
for (size_t i = 0; i < valueArray.size(); i++) {
74-
add_next_index_long(zv, static_cast<zend_long>(valueArray[i]));
75-
}
76-
}
77-
78-
template<typename TValue>
79-
static inline void assignZvalArray(zval* zv, std::vector<TValue>& valueArray, std::type_identity<double> zendType) {
80-
array_init_size(zv, valueArray.size());
81-
82-
for (size_t i = 0; i < valueArray.size(); i++) {
83-
add_next_index_double(zv, static_cast<double>(valueArray[i]));
84-
}
85-
}
86-
8751
template<typename TValue>
8852
using readTypeFunc_t = bool (*)(unsigned char* bytes, size_t used, size_t& offset, TValue& result);
8953

@@ -124,36 +88,6 @@ void ZEND_FASTCALL zif_unpackType(INTERNAL_FUNCTION_PARAMETERS) {
12488
readTypeCommon<TValue, readTypeFunc, TZendValue>(INTERNAL_FUNCTION_PARAM_PASSTHRU, reader);
12589
}
12690

127-
128-
template<typename TValue>
129-
using readTypeArrayFunc_t = bool (*) (unsigned char* bytes, size_t used, size_t& offset, size_t count, std::vector<TValue>& resultArray);
130-
131-
template<typename TValue, readTypeArrayFunc_t<TValue> readTypeArray, typename TZendValue>
132-
void ZEND_FASTCALL zif_readTypeArray(INTERNAL_FUNCTION_PARAMETERS) {
133-
zval* object_zv;
134-
zend_long zcount;
135-
byte_buffer_reader_zend_object* object;
136-
137-
ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2)
138-
Z_PARAM_OBJECT_OF_CLASS_EX(object_zv, byte_buffer_reader_ce, 0, 0)
139-
Z_PARAM_LONG(zcount)
140-
ZEND_PARSE_PARAMETERS_END_EX(return);
141-
142-
object = READER_FROM_ZVAL(object_zv);
143-
144-
if (zcount < 1) {
145-
zend_value_error("Count must be at least 1");
146-
return;
147-
}
148-
size_t count = static_cast<size_t>(zcount);
149-
150-
std::vector<TValue> resultArray;
151-
auto bytes = reinterpret_cast<unsigned char*>(ZSTR_VAL(object->reader.buffer));
152-
if (readTypeArray(bytes, ZSTR_LEN(object->reader.buffer), object->reader.offset, count, resultArray)) {
153-
assignZvalArray(return_value, resultArray, std::type_identity<TZendValue>{});
154-
}
155-
}
156-
15791
//this must be reimplemented for any new zend types handled, because ZPP macros can't be easily templated
15892
template<typename TValue, typename TZendValue>
15993
bool parseWriteTypeParams(zend_execute_data* execute_data, byte_buffer_writer_zend_object*& object, TValue& value, std::type_identity<TZendValue> zend_type) = delete;
@@ -263,73 +197,6 @@ void ZEND_FASTCALL zif_packType(INTERNAL_FUNCTION_PARAMETERS) {
263197
}
264198
}
265199

266-
267-
template<typename TValue, typename TZendValue>
268-
bool typeHashTableToArray(HashTable* valueArrayHt, std::vector<TValue>& valueArray, std::type_identity<TZendValue> zendType) = delete;
269-
270-
template<typename TValue>
271-
bool typeHashTableToArray(HashTable* valueArrayHt, std::vector<TValue>& valueArray, std::type_identity<zend_long> zendType) {
272-
zval* elementZv;
273-
ZEND_HASH_FOREACH_VAL(valueArrayHt, elementZv) {
274-
if (Z_TYPE_P(elementZv) != IS_LONG) {
275-
//TODO: give the correct array key when strings are used - I don't know how to do this in a non-awkward way currently
276-
zend_type_error("Array must contain only int, %s given at position %zu", zend_zval_type_name(elementZv), valueArray.size());
277-
return false;
278-
}
279-
TValue value = static_cast<TValue>(Z_LVAL_P(elementZv));
280-
valueArray.push_back(value);
281-
} ZEND_HASH_FOREACH_END();
282-
return true;
283-
}
284-
285-
template<typename TValue>
286-
bool typeHashTableToArray(HashTable* valueArrayHt, std::vector<TValue>& valueArray, std::type_identity<double> zendType) {
287-
zval* elementZv;
288-
ZEND_HASH_FOREACH_VAL(valueArrayHt, elementZv) {
289-
TValue value;
290-
auto elementType = Z_TYPE_P(elementZv);
291-
if (elementType == IS_DOUBLE) {
292-
value = static_cast<TValue>(Z_DVAL_P(elementZv));
293-
} else if (elementType == IS_LONG) {
294-
value = static_cast<TValue>(Z_LVAL_P(elementZv));
295-
} else {
296-
//TODO: give the correct array key when strings are used - I don't know how to do this in a non-awkward way currently
297-
zend_type_error("Array must contain only float, %s given at position %zu", zend_zval_type_name(elementZv), valueArray.size());
298-
return false;
299-
}
300-
valueArray.push_back(value);
301-
} ZEND_HASH_FOREACH_END();
302-
return true;
303-
}
304-
305-
template<typename TValue>
306-
using writeTypeArrayFunc_t = void (*)(zend_string*& buffer, size_t& offset, std::vector<TValue>& value);
307-
308-
template<typename TValue, writeTypeArrayFunc_t<TValue> writeTypeFunc, typename TZendValue>
309-
void ZEND_FASTCALL zif_writeTypeArray(INTERNAL_FUNCTION_PARAMETERS) {
310-
byte_buffer_writer_zend_object* object;
311-
zval* objectZv;
312-
HashTable* valueArrayHt;
313-
314-
ZEND_PARSE_PARAMETERS_START_EX(ZEND_PARSE_PARAMS_THROW, 2, 2)
315-
Z_PARAM_OBJECT_OF_CLASS_EX(objectZv, byte_buffer_writer_ce, 0, 0)
316-
Z_PARAM_ARRAY_HT(valueArrayHt)
317-
ZEND_PARSE_PARAMETERS_END();
318-
319-
object = WRITER_FROM_ZVAL(objectZv);
320-
321-
std::vector<TValue> valueArray;
322-
323-
if (!typeHashTableToArray(valueArrayHt, valueArray, std::type_identity<TZendValue>{})) {
324-
return;
325-
}
326-
327-
writeTypeFunc(object->writer.buffer, object->writer.offset, valueArray);
328-
if (object->writer.offset > object->writer.used) {
329-
object->writer.used = object->writer.offset;
330-
}
331-
}
332-
333200
ZEND_NAMED_FUNCTION(pmmp_encoding_private_constructor) {
334201
//NOOP
335202
}
@@ -376,51 +243,16 @@ ZEND_NAMED_FUNCTION(pmmp_encoding_private_constructor) {
376243
(writeFixedSizeType<native_type, byte_order>) \
377244
)
378245

379-
#define TYPE_ARRAY_ENTRIES(zend_name, native_type, zend_type, read_complex_type, write_complex_type) \
380-
BC_ZEND_RAW_FENTRY_WITH_DOC_COMMENT( \
381-
"read" zend_name "Array", \
382-
(zif_readTypeArray<native_type, read_complex_type, zend_type>), \
383-
arginfo_read_array, \
384-
read_##zend_type##_array_doc_comment \
385-
) \
386-
BC_ZEND_RAW_FENTRY_WITH_DOC_COMMENT( \
387-
"write" zend_name "Array", \
388-
(zif_writeTypeArray<native_type, write_complex_type, zend_type>), \
389-
arginfo_write_array, \
390-
write_##zend_type##_array_doc_comment \
391-
)
392-
393-
#define FIXED_TYPE_ARRAY_ENTRIES(zend_name, native_type, zend_type, byte_order) \
394-
TYPE_ARRAY_ENTRIES( \
395-
zend_name, \
396-
native_type, \
397-
zend_type, \
398-
(readFixedSizeTypeArray<native_type, byte_order>), \
399-
(writeFixedSizeTypeArray<native_type, byte_order>) \
400-
)
401-
402-
#define COMPLEX_TYPE_ARRAY_ENTRIES(zend_name, native_type, zend_type, read_element, write_element) \
403-
TYPE_ARRAY_ENTRIES( \
404-
zend_name, \
405-
native_type, \
406-
zend_type, \
407-
(readComplexTypeArray<native_type, read_element>), \
408-
(writeComplexTypeArray<native_type, write_element>) \
409-
)
410-
411246
#define FIXED_INT_BASE_ENTRIES(zend_name, native_type, byte_order) \
412-
FIXED_TYPE_ENTRIES(zend_name, native_type, zend_long, byte_order) \
413-
\
414-
FIXED_TYPE_ARRAY_ENTRIES(zend_name, native_type, zend_long, byte_order)
247+
FIXED_TYPE_ENTRIES(zend_name, native_type, zend_long, byte_order)
415248

416249
#define FIXED_INT_ENTRIES(zend_name, unsigned_native_type, signed_native_type, byte_order) \
417250
FIXED_INT_BASE_ENTRIES("Unsigned" zend_name, unsigned_native_type, byte_order) \
418251
\
419252
FIXED_INT_BASE_ENTRIES("Signed" zend_name, signed_native_type, byte_order)
420253

421254
#define FLOAT_ENTRIES(zend_name, native_type, byte_order) \
422-
FIXED_TYPE_ENTRIES(zend_name, native_type, double, byte_order) \
423-
FIXED_TYPE_ARRAY_ENTRIES(zend_name, native_type, double, byte_order)
255+
FIXED_TYPE_ENTRIES(zend_name, native_type, double, byte_order)
424256

425257
#define COMPLEX_INT_ENTRIES(zend_name, native_type, read_type, write_type) \
426258
TYPE_ENTRIES( \
@@ -429,13 +261,6 @@ ZEND_NAMED_FUNCTION(pmmp_encoding_private_constructor) {
429261
zend_long, \
430262
read_type, \
431263
write_type \
432-
) \
433-
COMPLEX_TYPE_ARRAY_ENTRIES( \
434-
zend_name, \
435-
native_type, \
436-
zend_long, \
437-
read_type, \
438-
write_type \
439264
)
440265

441266
//triad can't used readFixedSizeType because it's not a power of 2 bytes

tests/symmetry-tests.inc

Lines changed: 1 addition & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -22,49 +22,6 @@ function testSingleSymmetry(array $samples, \Closure $readMethod, \Closure $writ
2222
}
2323
}
2424

25-
/**
26-
* @phpstan-template TValue
27-
* @phpstan-param TValue[] $samples
28-
* @phpstan-param \Closure(ByteBufferReader,int):array<TValue> $readArrayMethod
29-
* @phpstan-param \Closure(ByteBufferWriter,array<TValue>):void $writeArrayMethod
30-
*/
31-
function testArraySymmetry(array $samples, \Closure $readArrayMethod, \Closure $writeArrayMethod) : void{
32-
$writer = new ByteBufferWriter();
33-
$writeArrayMethod($writer, $samples);
34-
35-
$reader = new ByteBufferReader($writer->getData());
36-
$decoded = $readArrayMethod($reader, count($samples));
37-
echo "--- array symmetry: match = " . ($samples === $decoded ? "YES" : "NO") . "\n";
38-
}
39-
40-
/**
41-
* @phpstan-template TValue
42-
* @phpstan-param TValue[] $samples
43-
* @phpstan-param \Closure(ByteBufferReader):TValue $readMethod
44-
* @phpstan-param \Closure(ByteBufferWriter,TValue):void $writeMethod
45-
* @phpstan-param \Closure(ByteBufferReader,int):array<TValue> $readArrayMethod
46-
* @phpstan-param \Closure(ByteBufferWriter,array<TValue>):void $writeArrayMethod
47-
*/
48-
function testArrayVsSingleSymmetry(array $samples, \Closure $readMethod, \Closure $writeMethod, \Closure $readArrayMethod, \Closure $writeArrayMethod) : void{
49-
$writer = new ByteBufferWriter();
50-
foreach($samples as $sample){
51-
$writeMethod($writer, $sample);
52-
}
53-
$reader = new ByteBufferReader($writer->getData());
54-
$decodedArray = $readArrayMethod($reader, count($samples));
55-
echo "--- array vs single symmetry: match = " . ($samples === $decodedArray ? "YES" : "NO") . "\n";
56-
57-
$writer = new ByteBufferWriter();
58-
$writeArrayMethod($writer, $samples);
59-
60-
$reader = new ByteBufferReader($writer->getData());
61-
$decodedSingles = [];
62-
for($i = 0, $size = count($samples); $i < $size; $i++){
63-
$decodedSingles[] = $readMethod($reader);
64-
}
65-
echo "--- single vs array symmetry: match = " . ($samples === $decodedSingles ? "YES" : "NO") . "\n";
66-
}
67-
6825
function testPackVsNormalSymmetry(array $samples, \Closure $readMethod, \Closure $writeMethod, \Closure $unpackMethod, \Closure $packMethod) : void{
6926
echo "--- single pack vs buffer read symmetry ---\n";
7027
foreach($samples as $sample){
@@ -91,16 +48,12 @@ function testPackVsNormalSymmetry(array $samples, \Closure $readMethod, \Closure
9148
* @phpstan-param TValue[] $samples
9249
* @phpstan-param \Closure(ByteBufferReader):TValue $readMethod
9350
* @phpstan-param \Closure(ByteBufferWriter,TValue):void $writeMethod
94-
* @phpstan-param \Closure(ByteBufferReader,int):array<TValue> $readArrayMethod
95-
* @phpstan-param \Closure(ByteBufferWriter,array<TValue>):void $writeArrayMethod
9651
* @phpstan-param \Closure(string):TValue $unpackMethod
9752
* @phpstan-param \Closure(TValue):string $packMethod
9853
*/
99-
function testFullSymmetry(string $label, array $samples, \Closure $readMethod, \Closure $writeMethod, \Closure $readArrayMethod, \Closure $writeArrayMethod, \Closure $unpackMethod, \Closure $packMethod) : void{
54+
function testFullSymmetry(string $label, array $samples, \Closure $readMethod, \Closure $writeMethod, \Closure $unpackMethod, \Closure $packMethod) : void{
10055
echo "########## TEST " . $label . " ##########\n";
10156
testSingleSymmetry($samples, $readMethod, $writeMethod);
10257
testPackVsNormalSymmetry($samples, $readMethod, $writeMethod, $unpackMethod, $packMethod);
103-
testArraySymmetry($samples, $readArrayMethod, $writeArrayMethod);
104-
testArrayVsSingleSymmetry($samples, $readMethod, $writeMethod, $readArrayMethod, $writeArrayMethod);
10558
echo "########## END TEST " . $label . " ##########\n\n";
10659
}

0 commit comments

Comments
 (0)