Why is substr-lvalue faster than four-arg substr?
Asked Answered
F

2

4

From this question, we benchmark these two variants,

substr( $foo, 0, 0 ) = "Hello ";
substr( $foo, 0, 0, "Hello " );

In it we discover that substr-lvalue is faster. To which Ikegami said,

How is 4-arg substr slower than lvalue substr (which must create a magical scalar, and requires extra operations)??? – ikegami

Truth be told, I also assumed that it would be massively slower and just mentioned it because it was brought up by someone else. Purely for curiosity,

Why is substr-lvalue faster than four-arg substr in the above usecase?

Fingerbreadth answered 8/5, 2020 at 1:25 Comment(1)
pp_substr and Perl_magic_setsubstr. The latter is called after the assigning to the magical scalar.Hoggish
H
4

It was simply a bad benchmark result.

When I replicated your results, I was using perl on Unbuntu on Windows Susbsytem for Linux. Let's just say that performance is sensitive to external factors on that system.

Even when using a native build for Windows (Strawberry Perl) on the same computer, I get wild differences in the results:

                   Rate        substr substr_valute   multiconcat
                  Rate substr_valute        substr   multiconcat
substr_valute 6997958/s            --           -0%          -27%
substr        7007667/s            0%            --          -26%
multiconcat   9533733/s           36%           36%            --

                   Rate        substr substr_valute   multiconcat
substr        6795650/s            --           -0%          -10%
substr_valute 6805545/s            0%            --          -10%
multiconcat   7526593/s           11%           11%            --

                    Rate        substr substr_valute   multiconcat
substr         7513339/s            --          -22%          -28%
substr_valute  9693997/s           29%            --           -6%
multiconcat   10367639/s           38%            7%            --

                    Rate        substr   multiconcat substr_valute
substr         8791152/s            --          -13%          -14%
multiconcat   10139954/s           15%            --           -1%
substr_valute 10240638/s           16%            1%            --

The times are just so small, and the machine is just too busy to get accurate readings.

(There's a point to be made about micro-optimizations in there somewhere...)

I hate running benchmarks on my shared linux web host, but it normally produces far more consistent results. Today was no exception.

                   Rate        substr substr_valute   multiconcat
substr        4293130/s            --           -3%          -13%
substr_valute 4407446/s            3%            --          -11%
multiconcat   4938717/s           15%           12%            --

                   Rate substr_valute        substr   multiconcat
substr_valute 4289732/s            --           -2%          -16%
substr        4356113/s            2%            --          -15%
multiconcat   5096889/s           19%           17%            --

(I used -3 instead of 100_000_000.)

All differences are 3% or less, which isn't significant. As far as I can tell, one isn't slower than the other.

In fact, one shouldn't expect any difference. As pointed out by Dave Mitchell, substr( $foo, 0, 0 ) = "Hello "; is optimized into something virtually equivalent to substr( $foo, 0, 0, "Hello " ); since 5.16 (with an improvement in 5.20).

$ perl -MO=Concise,-exec -e'substr( $foo, 0, 0, "Hello " );'
1  <0> enter
2  <;> nextstate(main 1 -e:1) v:{
3  <#> gvsv[*foo] s
4  <$> const[IV 0] s
5  <$> const[IV 0] s
6  <$> const[PV "Hello "] s
7  <@> substr[t2] vK/4
8  <@> leave[1 ref] vKP/REFC
-e syntax OK

$ perl -MO=Concise,-exec -e'substr( $foo, 0, 0 ) = "Hello ";'
1  <0> enter
2  <;> nextstate(main 1 -e:1) v:{
3  <$> const[PV "Hello "] s
4  <#> gvsv[*foo] s
5  <$> const[IV 0] s
6  <$> const[IV 0] s
7  <@> substr[t2] vKS/REPL1ST,3
8  <@> leave[1 ref] vKP/REFC
-e syntax OK

(The only difference is the order in which the operands are passed, which is signaled using the REPL1ST flag.)

Hoggish answered 8/5, 2020 at 1:57 Comment(7)
Yeah, for me on even with -3 I'm getting a gigantic difference on debian with Buster.Fingerbreadth
-3 vs 100_000_000 won't make a difference in anything but how long it runs (3 seconds vs 100_000_000 tests)Hoggish
For me substr_lvalue is around the same time as multiconcat, and always faster than substr on a Intel(R) Core(TM) i7-3520M CPU @ 2.90GHzFingerbreadth
We're talking of times around 100 ns per interation including a relatively expensive sub call. It's not surprising it's subject to fluctuations. When testing stuff like this, I use b_multiconcat => 'use strict; use warnings; for (1..1000) { my $foo = "world!"; $foo = "Hello $foo"; }'Hoggish
@Hoggish Just curious to know, what command you have used to get these results data?Juli
@vinodk89 See the link in the questionHoggish
Expanded my answer based on Dave Mitchell's comment-answerHoggish
S
4

Since 5.16.0, the lvalue+assign variant has been optimised into the 4-arg variant (although the nulled-out NOOP assignment op was still in the execution path until 5.20.0, which slowed it down slightly).

Superelevation answered 8/5, 2020 at 11:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.