[RFC] Working With Substrings

php.internals

Thomas Hruska

3 years ago
Hello Internals, I would like to start the discussion on adding several functions and parameters to existing functions for improved substring handling in PHP: https://wiki.php.net/rfc/working_with_substrings Please see the Open Issues section for a series of possible issues/questions that I anticipated could come up, but I'm sure there will be others. Some are technical questions related to the source code while others deal with bikeshedding (function names, etc).
-- Thomas Hruska CubicleSoft President CubicleSoft has over 80 original open source projects and counting. Plus a couple of commercial/retail products. What software are you looking to build?

Rowan Collins

3 years ago
On 14/02/2023 15:32, Thomas Hruska wrote:
> Hello Internals, > > I would like to start the discussion on adding several functions and > parameters to existing functions for improved substring handling in PHP: > > https://wiki.php.net/rfc/working_with_substrings
Hi Thomas, Thanks for your effort on this, I think efficient string handling functions would be a major help for the ecosystem, allowing library authors to do things in plain PHP code which currently defer to C extensions just for performance. My first thought opening the RFC was to see a function signature with 9 arguments and immediately wonder how to refactor it into something more manageable. Just writing *tests* for all the combinations sounds like a nightmare, let alone understanding code that uses them all. As I read through, I had a similar feeling about the need to copy-and-paste the same two parameters onto so many functions. Luckily, I think the RFC contains the seed of the solution to both problems: what you refer to as "virtual buffers". These seem to be crying out to be a new data type, with their own API - probably using OO style, given general fashions. Framed around that, I think we can split out a few different concerns: * Methods to take a string, and make a new, writeable buffer pointing at all or part of it * Methods to access parts of a buffer, as a string or another buffer * Methods to efficiently write to, delete from, or overwrite, parts of a buffer * Methods to explicitly manage the memory used by the buffer * Finally, support for writing to, or reading from, a buffer instead of a string in a number of existing functions Thinking about exactly what those methods should look like leads me to my next thought: we should be learning from prior art here. Are there other languages which already do this well, which PHP could emulate? Are there other languages which already do this *badly*, whose mistakes PHP could explicitly learn from? What comes to my mind immediately is that both Java and C# have "StringBuilder" classes, which cover at least some of these use cases. C#, in particular, had a lot of very smart people paid to design it, able to learn from mistakes Java had already made. Regards,
-- Rowan Tommins [IMSoP]

Thomas Hruska

3 years ago
On 2/14/2023 2:02 PM, Rowan Tommins wrote:
> On 14/02/2023 15:32, Thomas Hruska wrote: >> Hello Internals, >> >> I would like to start the discussion on adding several functions and >> parameters to existing functions for improved substring handling in PHP: >> >> https://wiki.php.net/rfc/working_with_substrings > > > Hi Thomas, > > Thanks for your effort on this, I think efficient string handling > functions would be a major help for the ecosystem, allowing library > authors to do things in plain PHP code which currently defer to C > extensions just for performance. > > > My first thought opening the RFC was to see a function signature with 9 > arguments and immediately wonder how to refactor it into something more > manageable. Just writing *tests* for all the combinations sounds like a > nightmare, let alone understanding code that uses them all.
I agree with and understand this sentiment.
> As I read through, I had a similar feeling about the need to > copy-and-paste the same two parameters onto so many functions. > > > Luckily, I think the RFC contains the seed of the solution to both > problems: what you refer to as "virtual buffers". These seem to be > crying out to be a new data type, with their own API - probably using OO > style, given general fashions.
I thought about that but didn't know how well it would be received nor, perhaps more importantly, the direction it should take (i.e. a formal Zend type in the engine, extending the existing zend_string type, a class, some combination, or something else entirely). All of the more advanced options I came up with would have required some code changes to the PHP source itself with a new data type being the most involved and probably the most controversial. As a result, I ended up deciding to go the "simple" function(s) route in the qolfuncs extension and then, after that, use the RFC process to kickstart the conversation while also showing a proof-of-concept that demonstrates performance can be notably improved in certain areas that have traditionally not done well. I figured it would be kind of difficult to get folks excited about strings/buffers (yawn!) if there weren't also some sort of ballpark in-context metrics/benchmarks to show the potential gains to make the effort worthwhile.
> Framed around that, I think we can split out a few different concerns: > > * Methods to take a string, and make a new, writeable buffer pointing at > all or part of it > * Methods to access parts of a buffer, as a string or another buffer > * Methods to efficiently write to, delete from, or overwrite, parts of a > buffer > * Methods to explicitly manage the memory used by the buffer > * Finally, support for writing to, or reading from, a buffer instead of > a string in a number of existing functions
Those sound fine. Just a couple thoughts: Being able to pass a new buffer type around to many of the same functions as zend_strings could introduce its own can of worms. Something to keep in mind for sure. Calling any function/method in PHP is an "expensive" operation. Once the code finally gets into the function body and past the input validation phase is when performant C routine calls can finally happen (e.g. native memcpy/memmove/memset calls that, in turn, use SIMD instructions). It's all the prior setup that takes the longest amount of time. I don't think there's a way around that without losing buffer overflow protections, which means there will ultimately always be a hard upper limit on what can be done in PHP userland. But we at least now know the userland performance ceiling for inline buffer manipulation is somewhere roughly around 2-3 times higher than current userland options on average. Based on the benchmarks I've run, that gain largely negates the function/method call overhead problem for the immediate future.
> Thinking about exactly what those methods should look like leads me to > my next thought: we should be learning from prior art here. Are there > other languages which already do this well, which PHP could emulate? Are > there other languages which already do this *badly*, whose mistakes PHP > could explicitly learn from? > > What comes to my mind immediately is that both Java and C# have > "StringBuilder" classes, which cover at least some of these use cases. > C#, in particular, had a lot of very smart people paid to design it, > able to learn from mistakes Java had already made.
Okay. I'm not entirely sure what the next step here should be. Should I go research the above, or go back and develop/test and then propose something concrete in an OO direction and gather feedback at that point, or should we hash it out a bit more here on the list to get a more specific direction to go in? Regardless, I've updated the RFC to reflect your response as Open Issue 9. Thank you for taking the time to look at the RFC and responding.
-- Thomas Hruska CubicleSoft President CubicleSoft has over 80 original open source projects and counting. Plus a couple of commercial/retail products. What software are you looking to build?

Rowan Collins

3 years ago
On 15 February 2023 02:35:42 GMT, Thomas Hruska <thruska@cubiclesoft.com> wrote:
>On 2/14/2023 2:02 PM, Rowan Tommins wrote: >I thought about that but didn't know how well it would be received nor, perhaps more importantly, the direction it should take (i.e. a formal Zend type in the engine, extending the existing zend_string type, a class, some combination, or something else entirely). All of the more advanced options I came up with would have required some code changes to the PHP source itself with a new data type being the most involved and probably the most controversial.
My instinct was that it could just be a built-in class, with an internal pointer to a zend_string that's completely invisible to userland. Something like how the SimpleXML and DOM objects just point into a libxml parse result. Then to add to existing functions requires changing an argument type from string to string|Buffer, rather than adding new arguments. No change to the type system needed, internally or externally, just some code to unwrap the pointer. But perhaps I'm being naive and oversimplifying, as I don't have a deep understanding of the engine.
>I'm not entirely sure what the next step here should be. Should I go research the above, or go back and develop/test and then propose something concrete in an OO direction and gather feedback at that point, or should we hash it out a bit more here on the list to get a more specific direction to go in?
Well, those were just my thoughts; maybe someone else will come along shortly with a very different take. Regards,
-- Rowan Tommins [IMSoP]

Rowan Collins

3 years ago
On 15 February 2023 05:18:50 GMT, Rowan Tommins <rowan.collins@gmail.com> wrote:
>My instinct was that it could just be a built-in class, with an internal pointer to a zend_string that's completely invisible to userland. Something like how the SimpleXML and DOM objects just point into a libxml parse result.
To make this a bit more concrete, what I was picturing was that instead of this example: str_splice($this->pagemap[$pagepos][0], $x2, $size2, $data, $x, $size); You would have something like this: // Wrap an existing zend_string in an object $destBuffer = Buffer:: fromString($this->pagemap[$pagepos][0]); // Similar, but also track start and end offsets $sourceBuffer = Buffer::fromSubString($data, $x, $size); // Now do the actual memory copy $destBuffer->splice($x2, $size2, $sourceBuffer); The explicit size handling parameters of str_splice, and the str_realloc function, would be replaced with methods to get and set the allocated length of a Buffer object. The buffer would only be shrunk when requested, or when cast to string. The $src_repeat argument feels somewhat out of place in a "splice" operation anyway, and perhaps should be part of a different method. On a different note, don't forget that we have named parameters now, which is a big help with signatures like this; this example: $vsize = str_splice($str, $pos, $pos2 - $pos + 1, $embed, 0, null, 1, false, $vsize); Looks slightly less scary written like this: $vsize = str_splice($str, $pos, $pos2 - $pos + 1, $embed, shrink: false, dst_lastsize: $vsize); Regards,
-- Rowan Tommins [IMSoP]

Lydia de Jongh

3 years ago
Hi, Very interesting topic! On which I have NO experience 🙈 Op wo 15 feb. 2023 om 08:02 schreef Rowan Tommins <rowan.collins@gmail.com>:
> On 15 February 2023 05:18:50 GMT, Rowan Tommins <rowan.collins@gmail.com> > wrote: > >My instinct was that it could just be a built-in class, with an internal > pointer to a zend_string that's completely invisible to userland. Something > like how the SimpleXML and DOM objects just point into a libxml parse > result. > > To make this a bit more concrete, what I was picturing was that instead of > this example: > > str_splice($this->pagemap[$pagepos][0], $x2, $size2, $data, $x, $size); > > You would have something like this: > > // Wrap an existing zend_string in an object > $destBuffer = Buffer:: fromString($this->pagemap[$pagepos][0]); > // Similar, but also track start and end offsets > $sourceBuffer = Buffer::fromSubString($data, $x, $size); > // Now do the actual memory copy > $destBuffer->splice($x2, $size2, $sourceBuffer); > > >
In some other languages every variable IS an object..... by default. As far as I understand, the code above is meant as internal. But what if any variable is a small object. Has this been ever considered? Or would it use too much performance? $oString = 'my text'; $oString->toUpper(); echo $oString; // 'MY TEXT' Greetz, Lydia

Thomas Hruska

3 years ago
On 2/15/2023 6:03 AM, Lydia de Jongh wrote:
> Hi, > Very interesting topic! On which I have NO experience 🙈 > > > In some other languages every variable IS an object..... by default. > > As far as I understand, the code above is meant as internal. > But what if any variable is a small object. > Has this been ever considered? Or would it use too much performance? > > $oString = 'my text'; > > $oString->toUpper(); > > echo $oString; // 'MY TEXT'
The above represents a significant amount of scope creep but it's certainly interesting. So let's explore it a bit and gauge the response. The above code will currently throw an error. Significant global adoption of such a change will take a fairly long time - probably a decade, maybe longer. AFAIK, there is nothing technically preventing the core Zend engine from accepting a -> token after a string variable and calling a function that performs an inline modification of the string. As a brief test, I just ran the example code through PHP and got: "PHP Fatal error: Uncaught Error: Call to a member function toUpper() on string in test.php:4" The error message shows that Zend engine clearly already recognizes toUpper() as an attempted function/method call on a string...it just doesn't know what to do with it. So the logic for supporting -> method calls on strings appears, at least from my very brief test, to already be mostly in place. Nice! Supporting this would likely result in two distinct internal functions that would have to be maintained. One inline string-object method variant that can avoid copy-on-write (e.g. $var->toUpper()) and one that only does copy-on-write (e.g. strtoupper()). Repeat that for all of the existing string functions. Alternatively, the main function body for each function could move into its own function that has a parameter for distinguishing the difference between "function (copy) vs. method (possibly inline)" calls, which would create some additional overhead for the existing ext/standard/string functions. The average performance loss for regular function calls would need to be benchmarked. Nobody likes seeing performance losses even if they end up being a less than 1% reduction. C function calls are way faster than PHP userland but they still have some overhead. This is just a thought exploration of how it could be implemented. With this approach, a $var->repeat("\x00", 4096, 50) could work to start at position 50 and write 4,096 zero bytes. But that again adds a parameter for an offset. But maybe $var[50...4096 + 50]->repeat("\x00", 4096) could solve that? That's a bit awkward to look at, requires adding range support to strings (and maybe arrays too because you know someone will want that as well), and probably breaks a lot of things. However, I'm not sure this idea can be used with virtual buffers that expressly set their size. zend_string (how strings are stored) simply doesn't have support for it. There's a length member but no size member. Internally, the zend_string implementation assumes length + 1 = size. If you got this far and know how PHP, C, and CPU hardware works, you can skip ahead to the last two paragraphs. The next few paragraphs delves into some details to try to explain to Lydia (and others who are following along) what's going on under the hood with why I focused on substrings. Apologies in advance for my rambling. Avoiding copy-on-write requires the internal reference count total (refcount) to effectively be 1. Reference counting helps reduce the number of times a copy is made. Fewer copies generally results in faster performance. A refcount of 1 does happen more frequently when inside a loop. In real world code, depending on what is being done, the first loop iteration might have many references to a string while the second loop iteration that is operating on the same data might have a refcount of just one. This situation happens frequently enough to consider inline options. Memory allocation is one of the slower operations in computer programs. Ideally, a program makes as few allocation requests to the system as possible. PHP avoids making system calls to allocate memory by pooling reclaimed memory into multiple memory pools for reuse. Copying strings from one buffer to another buffer is also avoided by leveraging reference counting. However, this creates the scenario where every modified string has its buffer copied from one buffer to the next. Let's take this fairly common but simple code to see what happens in Zend engine: $pos = strrpos($str, "/"); $str = substr($str, 0, $pos + 1); The above substr() results in one "logical" memory allocation and one logical free operation (whether it actually makes system calls to allocate/free memory is way beyond the scope of this paragraph) and one memory copy operation. We say we want the substring of a certain size, which allocates space to create a temporary copy that can hold that string. Then the data is copied from one buffer to another buffer. Then we assign the temporary copy to the original input variable. That causes the original value, assuming nothing else is referencing it (aka a refcount of 0), to eventually re-enter the memory pool for future allocations and assigns the temporary to the variable. All of that is done transparently to the user so the user generally doesn't have to worry about memory allocation strategies. There's no good way to detect this situation to optimize it, although I'm sure the JIT does try to do so on some level when it is enabled. As a side-effect, there are also no built-in tools currently available to care about memory allocation strategies for individual allocations when the need does arise. There are some controls for managing garbage collection but those have global impact. Doing that operation one time is fast enough and not really a problem. Doing it 1,000,000 times in a loop is where we end up constantly copying memory around when we could potentially work on the same memory buffer the entire time. We still might end up using the same memory buffers over and over due to recycling them through the PHP memory pool, which means the buffers might get to sit in the L1 or L2 cache in the CPU, but it does leave some performance on the table because copying a buffer or portions of it repeatedly can be an unnecessary operation. Buffers that are larger than the CPU's cache line sizes are going to suffer the most because there will be constant requests to main memory for the information that the CPU needs to modify and will constantly flush the cache lines and stall out while waiting for more data to arrive. That's not exactly optimal/ideal. Modifying the same buffer inline will be more likely stay in the L1 and L2 cache lines and therefore be much closer to the CPU core, resulting in notably faster performance. Pointers in C are much faster than copying memory. The problem is exposing pointers to userland, especially in Internet-facing software. Pointers are notoriously unsafe - just look at the zillion buffer overflow vulnerabilities (CVEs) that are reported annually across all software products. Copy-on-write, by comparison, is a much safer operation at the cost of performance. However, pointers let us just point at a substring or general chunk of memory instead of copying it, which significantly reduces the overhead since pointers are simple integer values that contain a memory address. And those values are small enough to sit in CPU registers, which are blazing fast. CPUs only have a handful of registers though because each register dramatically increases the cost of the CPU die. So if we can just point at the memory we want to "extract" instead of actually copying the data into its own string object, we can potentially save a ton of CPU cycles, especially when working with data inside a loop. Overall, I think substrings offer the most obvious/apparent area for performance gains and probably have, implementation details aside, the least amount of friction. But maybe we should consider the larger ecosystem of string functions as well? Or should this just be a possible longer term idea that requires more thought and research and thus the scope should be limited and we put Lydia's idea under Future Scope in the RFC? Other thoughts/comments? Added as Open Issue 10 to the RFC. Thank you for your input.
-- Thomas Hruska CubicleSoft President CubicleSoft has over 80 original open source projects and counting. Plus a couple of commercial/retail products. What software are you looking to build?

Larry Garfield

3 years ago
On Wed, Feb 15, 2023, at 1:35 PM, Thomas Hruska wrote:
> On 2/15/2023 6:03 AM, Lydia de Jongh wrote: >> Hi, >> Very interesting topic! On which I have NO experience 🙈 >> >> >> In some other languages every variable IS an object..... by default. >> >> As far as I understand, the code above is meant as internal. >> But what if any variable is a small object. >> Has this been ever considered? Or would it use too much performance? >> >> $oString = 'my text'; >> >> $oString->toUpper(); >> >> echo $oString; // 'MY TEXT' > > The above represents a significant amount of scope creep but it's > certainly interesting. So let's explore it a bit and gauge the response. > > The above code will currently throw an error. Significant global > adoption of such a change will take a fairly long time - probably a > decade, maybe longer. > > AFAIK, there is nothing technically preventing the core Zend engine from > accepting a -> token after a string variable and calling a function that > performs an inline modification of the string. > > As a brief test, I just ran the example code through PHP and got: "PHP > Fatal error: Uncaught Error: Call to a member function toUpper() on > string in test.php:4" The error message shows that Zend engine clearly > already recognizes toUpper() as an attempted function/method call on a > string...it just doesn't know what to do with it. So the logic for > supporting -> method calls on strings appears, at least from my very > brief test, to already be mostly in place. Nice!
*snip* What you're describing here is "scalar methods", which has been discussed on and off for many years. The idea has its proponents, but also its detractors. (I'm in the detractor camp, personally, as I think there are better, more flexible options.) I would strongly recommend not allowing "faster string manipulation" to scope creep into scalar methods, as that will almost guarantee that it never comes to fruition. :-) IF scalar methods were to happen, they should happen on their own. --Larry Garfield

Derick Rethans

3 years ago
On 15 February 2023 13:03:39 GMT, Lydia de Jongh <flexjoly@gmail.com> wrote:
>Hi, >Very interesting topic! On which I have NO experience 🙈 > > > >Op wo 15 feb. 2023 om 08:02 schreef Rowan Tommins <rowan.collins@gmail.com>: > >> On 15 February 2023 05:18:50 GMT, Rowan Tommins <rowan.collins@gmail.com> >> wrote: >> >My instinct was that it could just be a built-in class, with an internal >> pointer to a zend_string that's completely invisible to userland. Something >> like how the SimpleXML and DOM objects just point into a libxml parse >> result. >> >> To make this a bit more concrete, what I was picturing was that instead of >> this example: >> >> str_splice($this->pagemap[$pagepos][0], $x2, $size2, $data, $x, $size); >> >> You would have something like this: >> >> // Wrap an existing zend_string in an object >> $destBuffer = Buffer:: fromString($this->pagemap[$pagepos][0]); >> // Similar, but also track start and end offsets >> $sourceBuffer = Buffer::fromSubString($data, $x, $size); >> // Now do the actual memory copy >> $destBuffer->splice($x2, $size2, $sourceBuffer); >> >> >> > >In some other languages every variable IS an object..... by default. > >As far as I understand, the code above is meant as internal. >But what if any variable is a small object. >Has this been ever considered? Or would it use too much performance? > >$oString = 'my text'; > >$oString->toUpper(); > >echo $oString; // 'MY TEXT' > > > >Greetz, Lydia
https://wiki.php.net/rfc/unicode_text_processing And yes, that won't be as fast as just calling strtoupper. cheers Derick

Lydia de Jongh

3 years ago
Hi Derick, Thomas, Op do 16 feb. 2023 om 08:57 schreef Derick Rethans <derick@php.net>:
> > > https://wiki.php.net/rfc/unicode_text_processing > > And yes, that won't be as fast as just calling strtoupper. > > cheers > Derick >
Looks great!!! Complex string manipulation inside an object will be faster then all copying variables around in memory, like Thomas kindly explained in his post. If I understand correctly.... And it would make php even more mature, gaining from more OOP. Op wo 15 feb. 2023 om 20:35 schreef Thomas Hruska <thruska@cubiclesoft.com>:
> <......>
Doing that operation one time is fast enough and not really a problem.
> Doing it 1,000,000 times in a loop is where we end up constantly copying > memory around when we could potentially work on the same memory buffer > the entire time. We still might end up using the same memory buffers > over and over due to recycling them through the PHP memory pool, which > means the buffers might get to sit in the L1 or L2 cache in the CPU, but > it does leave some performance on the table because copying a buffer or > portions of it repeatedly can be an unnecessary operation. Buffers that > are larger than the CPU's cache line sizes are going to suffer the most > because there will be constant requests to main memory for the > information that the CPU needs to modify and will constantly flush the > cache lines and stall out while waiting for more data to arrive. That's > not exactly optimal/ideal. Modifying the same buffer inline will be > more likely stay in the L1 and L2 cache lines and therefore be much > closer to the CPU core, resulting in notably faster performance. > Pointers in C are much faster than copying memory. The problem is > exposing pointers to userland, especially in Internet-facing software. > Pointers are notoriously unsafe - just look at the zillion buffer > overflow vulnerabilities (CVEs) that are reported annually across all > software products. Copy-on-write, by comparison, is a much safer > operation at the cost of performance. However, pointers let us just > point at a substring or general chunk of memory instead of copying it, > which significantly reduces the overhead since pointers are simple > integer values that contain a memory address. And those values are > small enough to sit in CPU registers, which are blazing fast. CPUs only > have a handful of registers though because each register dramatically > increases the cost of the CPU die. So if we can just point at the
memory we want to "extract" instead of actually copying the data into
> its own string object, we can potentially save a ton of CPU cycles, > especially when working with data inside a loop. > > > Overall, I think substrings offer the most obvious/apparent area for > performance gains and probably have, implementation details aside, the > least amount of friction. But maybe we should consider the larger > ecosystem of string functions as well? Or should this just be a > possible longer term idea that requires more thought and research and > thus the scope should be limited and we put Lydia's idea under Future > Scope in the RFC? Other thoughts/comments? > > Added as Open Issue 10 to the RFC. Thank you for your input. > > Thomas Hruska >
Thanks for your kind and extended explanation. I know a little about the memory allocations. But I am not sure about what to conclude from your explanation. If an object would take less copying around or not. This memory conversation brings up other old memories ☺... peek, pook, assembly etc 😍 Greetz, flexJoly (aka Lydia)

Christoph Becker

2 years ago
On 15.02.2023 at 06:18, Rowan Tommins wrote:
> On 15 February 2023 02:35:42 GMT, Thomas Hruska <thruska@cubiclesoft.com> wrote: > >> On 2/14/2023 2:02 PM, Rowan Tommins wrote: >> >> I thought about that but didn't know how well it would be received nor, perhaps more importantly, the direction it should take (i.e. a formal Zend type in the engine, extending the existing zend_string type, a class, some combination, or something else entirely). All of the more advanced options I came up with would have required some code changes to the PHP source itself with a new data type being the most involved and probably the most controversial. > > My instinct was that it could just be a built-in class, with an internal pointer to a zend_string that's completely invisible to userland. Something like how the SimpleXML and DOM objects just point into a libxml parse result. > > Then to add to existing functions requires changing an argument type from string to string|Buffer, rather than adding new arguments. > > No change to the type system needed, internally or externally, just some code to unwrap the pointer. But perhaps I'm being naive and oversimplifying, as I don't have a deep understanding of the engine. > >> I'm not entirely sure what the next step here should be. Should I go research the above, or go back and develop/test and then propose something concrete in an OO direction and gather feedback at that point, or should we hash it out a bit more here on the list to get a more specific direction to go in? > > Well, those were just my thoughts; maybe someone else will come along shortly with a very different take.
I'm very late on this discussion, but I think it is an interesting topic, and maybe <https://github.com/cmb69/php-stringbuilder>, which I had written long ago just to check some assumptions, can serve as POC. It is certainly possible to have such a string buffer class without having to patch the engine; it could even be made available as PECL extension (first). Note that this StringBuilder uses `smart_str`s[1] what might be a good idea or not. But certainly you could use some other internal handling; interoperability with `zend_string`s[2] requires to copy the char arrays in most cases anyway, since these have a fixed length, and if these copies are reduced to a minimum (i.e. the new class has enough flexibility to work without casting to and from string), that should be bearable. Not sure if that would work for the "gd imageexportpixels() and imageimportpixels()" RFC[3], but it might be worth investigating. [1] <https://www.phpinternalsbook.com/php7/internal_types/strings/smart_str.html> [2] <https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html> [3] <https://wiki.php.net/rfc/gd_image_export_import_pixels> Cheers, Christoph

Rob Landers

2 years ago
On Sat, Jul 27, 2024, at 15:26, Christoph M. Becker wrote:
> On 15.02.2023 at 06:18, Rowan Tommins wrote: > > > On 15 February 2023 02:35:42 GMT, Thomas Hruska <thruska@cubiclesoft.com> wrote: > > > >> On 2/14/2023 2:02 PM, Rowan Tommins wrote: > >> > >> I thought about that but didn't know how well it would be received nor, perhaps more importantly, the direction it should take (i.e. a formal Zend type in the engine, extending the existing zend_string type, a class, some combination, or something else entirely). All of the more advanced options I came up with would have required some code changes to the PHP source itself with a new data type being the most involved and probably the most controversial. > > > > My instinct was that it could just be a built-in class, with an internal pointer to a zend_string that's completely invisible to userland. Something like how the SimpleXML and DOM objects just point into a libxml parse result. > > > > Then to add to existing functions requires changing an argument type from string to string|Buffer, rather than adding new arguments. > > > > No change to the type system needed, internally or externally, just some code to unwrap the pointer. But perhaps I'm being naive and oversimplifying, as I don't have a deep understanding of the engine. > > > >> I'm not entirely sure what the next step here should be. Should I go research the above, or go back and develop/test and then propose something concrete in an OO direction and gather feedback at that point, or should we hash it out a bit more here on the list to get a more specific direction to go in? > > > > Well, those were just my thoughts; maybe someone else will come along shortly with a very different take. > > I'm very late on this discussion, but I think it is an interesting > topic, and maybe <https://github.com/cmb69/php-stringbuilder>, which I > had written long ago just to check some assumptions, can serve as POC. > It is certainly possible to have such a string buffer class without > having to patch the engine; it could even be made available as PECL > extension (first). > > Note that this StringBuilder uses `smart_str`s[1] what might be a good > idea or not. But certainly you could use some other internal handling; > interoperability with `zend_string`s[2] requires to copy the char arrays > in most cases anyway, since these have a fixed length, and if these > copies are reduced to a minimum (i.e. the new class has enough > flexibility to work without casting to and from string), that should be > bearable. > > Not sure if that would work for the "gd imageexportpixels() and > imageimportpixels()" RFC[3], but it might be worth investigating. > > [1] > <https://www.phpinternalsbook.com/php7/internal_types/strings/smart_str.html> > [2] > <https://www.phpinternalsbook.com/php7/internal_types/strings/zend_strings.html> > [3] <https://wiki.php.net/rfc/gd_image_export_import_pixels> > > Cheers, > Christoph >
Huh, I am also very late and somewhat poignant, last weekend, I managed to refactor all zend_strings to contain a char* instead of char[1] and the char* pointed to the memory just after the pointer. It increased zend_string by a few bytes on a 64bit machine, but would allow for some nice optimizations, such as zend_strings sharing memory (effectively removing the need for the current interned strings implementation). I ended up ditching it because it would break literally every extension that does its own allocations instead of calling zend_string_alloc|init() and it was also hard to manage when copying strings, which also some core extensions do instead of calling core zend_string_* functions. Needless to say, "vanilla php" worked fine and all tests passed. I did submit a small part of my refactoring here: https://github.com/php/php-src/pull/15054 but even something that simple didn't seem well received. So, I won't continue this approach. But, fwiw, I wouldn't advise changing zend_strings too much, many extensions appear to do one of two things: their own allocations and/or their own copying and/or their own freeing. — Rob