Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/perl5Public

Perl_do_print: stringify an SVt_IV IV/UV more efficiently#22927

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
richardleach merged 5 commits intoPerl:bleadfromrichardleach:hydahy/iv2buf
Feb 10, 2025

Conversation

richardleach
Copy link
Contributor

https://stackoverflow.com/questions/79244888 points out thatpp_print
is really slow at stringifying integers. It's bizarrely much faster to add
additional operations to do the stringification first.

Here are some sample timings:

1003 ms | 'for (my $i=1; $i<=10000000;$i++) { say $i }' 630 ms | 'for (my $i=1; $i<=10000000;$i++) { say "$i" }' 695 ms | 'for (my $i=1; $i<=10000000;$i++) { say $i."" }' 553 ms | 'my $i = "str"; for ($i=1; $i<=10000000;$i++) { say $i }'

Note from the last timing that stringification inpp_print isnot
always slow, only when the IV is stored in a SVt_IV. This traces back
toPerl_do_print which has a special path for just this case:

    if (SvTYPE(sv) == SVt_IV && SvIOK(sv)) {        assert(!SvGMAGICAL(sv));        if (SvIsUV(sv))            PerlIO_printf(fp, "%" UVuf, (UV)SvUVX(sv));        else            PerlIO_printf(fp, "%" IVdf, (IV)SvIVX(sv));        return !PerlIO_error(fp);    }

(There are no code comments to say why it does this. Perhaps deliberately
preserving the type for some reason?)

PerlIO_printf callsPerlIO_vprintf, which callsPerl_vnewSVpvf, which
creates a new (effectively) temporary SV, then sends that to
Perl_sv_vcatpvfn_flags for stringification and storage of the IV.
PerlIO_vprintf then writes out the buffer from the temp SV and frees it.

All the other routes essentially callSvPV_const on$i or aPADTMP.
This invokessv_2pv_flags and its helper,S_uiv_2buf, does the work.
No temporary SVs are created and freed in such cases.

This PR changesPerl_do_print to also use a small buffer and
S_uiv_2buf to do the stringification more efficiently, whilst still
avoiding a type upgrade, Re-measuring after this PR:

 470 ms 'for (my $i=1; $i<=10000000;$i++) { say $i }'

  • This set of changes requires a perldelta entry, and it is included.

@richardleachrichardleach changed the titlePerl_do_print: stringify an SVt_IV IV more efficientlyPerl_do_print: stringify an SVt_IV IV/UV more efficientlyJan 17, 2025
Copy link
Contributor

@khwilliamsonkhwilliamson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Note most of my comments were about the original that got moved. I think there should be a commit with just the move; then another to enhance the resulting one.

LGTM besides mauke's points

sv_inline.h Outdated
word_ptr = (U16*)ptr;
word_table = (U16*)int2str_table.arr;

if (UNLIKELY(is_uv))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Why the branch prediction? In the next commit, I don't see why this would be unlikely to be called

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

That was pre-existing. I agree though and will remove the UNLIKELY.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

UVs are super rare. I'd argue >99% of all integers are stored as IVs.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Note that something likemy $foo = 100000 isn't an UV, despite not having a sign and being representable as an UV.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Is there likely any benefit in defaulting sign to zero and changing the code to something like:

   if (!is_uv) {       if (iv >= 0) {           uv = iv;        } else {            /* This is NEGATE_2UV(iv), which can be found in handy.h. */            /* sv_inline.h does not include handy.h because the latter             * would then get included twice into .c files. */            uv = (ASSUME((iv) < 0), (UV)-((iv) + 1) + 1U);            sign = 1;        }   }

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

On GCC generated code will be identical:https://gcc.godbolt.org/z/bj9x4xnTz

BTW, "extract the sign and convert to UV" is a common pattern in Perl code. I experimented with a similar snippet extracted frompp_multiply, I tried to make the IV part branchless, but it turned out to be a pessimization. On my PC, the branchless version is 2x slower. The benchmark assumes 100% correct prediction, which I think would be close enough to real life.

Here's the code I benchmarked:

/* branchless.c */_Boolauvok,buvok;unsigned long longalow,blow;/* this may not look branchless, but it actually compiles to branchless code */voidto_uv(long longaiv,long longbiv) {alow=aiv >=0 ?aiv :-(unsigned long long)aiv;auvok=aiv >=0;blow=biv >=0 ?biv :-(unsigned long long)biv;buvok=biv >=0;}/* branching.c */_Boolauvok,buvok;unsigned long longalow,blow;voidto_uv(long longaiv,long longbiv) {if (aiv >=0) {alow=aiv;auvok=1;/* effectively it's a UV now */    }else {/* abs, auvok == false records sign */alow=-(unsigned long long)aiv;    }if (biv >=0) {blow=biv;buvok=1;/* effectively it's a UV now */    }else {/* abs, buvok == false records sign */blow=-(unsigned long long)biv;    }}/* main.c */voidto_uv(long longaiv,long longbiv);intmain() {for (long longi=0;i<1000000000;++i) {to_uv(i,i+1);    }return0;}

(main andto_uv need to be in separate files to prevent constant folding)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

The benchmark assumes 100% correct prediction, which I think would be close enough to real life.

Well. It depends. If the code is mixing negative and positive numbers then the branch prediction will be all over the place. Ithink that multiplication is typically done with two positive integers. But then again it's not like mixing signs is something unusual.

It's always difficult to decide which trade-off is right.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Ok, I tried a few variations with these three, non-UV cases:

  1. 'for (my $i=1; $i<=10000000;$i++) { say $i }'

UNCHANGED - -if (UNLIKELY(is_uv))

            486.07 msec task-clock                       #    0.969 CPUs utilized               2,062,905,670      cycles                           #    4.244 GHz                         7,040,218,542      instructions                     #    3.41  insn per cycle              1,664,254,169      branches                         #    3.424 G/sec

NO_UNLIKELY -if (is_uv)

            469.99 msec task-clock                       #    0.934 CPUs utilized               2,008,565,577      cycles                           #    4.274 GHz                         7,029,774,550      instructions                     #    3.50  insn per cycle              1,654,155,079      branches                         #    3.520 G/sec

REFACTOR - initialize sign to zero, thenif (!is_uv) {

            460.94 msec task-clock                       #    0.939 CPUs utilized               2,018,454,841      cycles                           #    4.379 GHz                         7,030,210,591      instructions                     #    3.48  insn per cycle              1,654,255,171      branches                         #    3.589 G/sec

REFACTOR+LIKELY - initialize sign to zero, thenif (LIKELY(!is_uv)) {

           468.79 msec task-clock                       #    0.999 CPUs utilized               2,069,028,677      cycles                           #    4.414 GHz                         7,039,775,425      instructions                     #    3.40  insn per cycle              1,664,153,959      branches                         #    3.550 G/sec
  1. 'for (my $i=1; $i<=10000000;$i++) { say -$i }'

UNCHANGED - -if (UNLIKELY(is_uv))

            526.16 msec task-clock                       #    0.972 CPUs utilized               2,217,557,251      cycles                           #    4.215 GHz                         7,553,385,689      instructions                     #    3.41  insn per cycle              1,755,583,715      branches                         #    3.337 G/sec

NO_UNLIKELY -if (is_uv)

            501.48 msec task-clock                       #    0.952 CPUs utilized               2,109,110,712      cycles                           #    4.206 GHz                         7,563,289,130      instructions                     #    3.59  insn per cycle              1,765,558,885      branches                         #    3.521 G/sec

REFACTOR - initialize sign to zero, thenif (!is_uv) {

            482.06 msec task-clock                       #    0.928 CPUs utilized               2,129,078,811      cycles                           #    4.417 GHz                         7,563,276,997      instructions                     #    3.55  insn per cycle              1,765,556,805      branches                         #    3.663 G/sec

REFACTOR+LIKELY - initialize sign to zero, thenif (LIKELY(!is_uv)) {

            468.20 msec task-clock                       #    0.999 CPUs utilized               2,166,095,827      cycles                           #    4.626 GHz                         7,553,254,402      instructions                     #    3.49  insn per cycle              1,755,552,941      branches                         #    3.750 G/sec
  1. 'my $x; for (my $i=1; $i<=10000000;$i++) { ($i % 2) ? say $x : say -$x}'

UNCHANGED - -if (UNLIKELY(is_uv))

            701.13 msec task-clock                       #    0.948 CPUs utilized               2,997,635,522      cycles                           #    4.275 GHz                         9,450,504,662      instructions                     #    3.15  insn per cycle              2,422,527,385      branches                         #    3.455 G/sec

NO_UNLIKELY -if (is_uv)

            670.31 msec task-clock                       #    0.958 CPUs utilized               2,867,434,790      cycles                           #    4.278 GHz                         9,450,450,298      instructions                     #    3.30  insn per cycle              2,422,513,128      branches                         #    3.614 G/sec

REFACTOR - initialize sign to zero, thenif (!is_uv) {

            692.92 msec task-clock                       #    0.976 CPUs utilized               3,013,950,916      cycles                           #    4.350 GHz                         9,450,506,408      instructions                     #    3.14  insn per cycle              2,422,528,389      branches                         #    3.496 G/sec

REFACTOR+LIKELY - initialize sign to zero, thenif (LIKELY(!is_uv)) {

            671.45 msec task-clock                       #    0.975 CPUs utilized               3,009,114,252      cycles                           #    4.482 GHz                         9,450,461,477      instructions                     #    3.14  insn per cycle              2,422,517,655      branches                         #    3.608 G/sec

Results varied by +/-50msec (at least) per run, and I'm not saying the above timings were mean or median. It seems hard to distinguish real differences from noise. The variants without branch prediction hints have about the same number of instructions and branches, as do the variants with hints, which isn't a surprise. Maybe there's a slight benefit to intializingint sign = 0; and simplifying the source code, so I'm going to go with that. I'll leave the branch prediction hint in for now, it can always be changed later.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Results varied by +/-50msec (at least) per run, and I'm not saying the above timings were mean or median. It seems hard to distinguish real differences from noise.

You really need more than one trial for this type of benchmarking so you can see the variance, when using perf directly I tend to do three, but that's mostly because the perf UI is awfulness for automation (and I'm lazy).

My WIP benchmarking does 10 trials (and takes the median), but I expect to increase that to 20 or more.

@richardleach
Copy link
ContributorAuthor

I've made changes in response to each of the comments so far. Please check that you're happy with those changes and the overall state of the PR. (I'll rebase after tomorrow's blead-point release.)

sv_inline.h Outdated
/* This is NEGATE_2UV(iv), which can be found in handy.h. */
/* sv_inline.h does not include handy.h because the latter
* would then get included twice into .c files. */
uv = (ASSUME((iv) < 0), (UV)-((iv) + 1) + 1U);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

handy.h, like most .h files has a guard against getting included twice. Did you try it?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I admit not, but something goes wrong with DynaLoader if I do:

cc -c   -fwrapv -DDEBUGGING -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2 -Wall -Werror=pointer-arith -Werror=vla -Wextra -Wno-long-long -Wno-declaration-after-statement -Wc++-compat -Wwrite-strings -Wno-use-after-free -O2 -g   -DVERSION=\"1.57\" -DXS_VERSION=\"1.57\"  "-I../.."  -DLIBC="/lib/x86_64-linux-gnu/libc.so.6" DynaLoader.cIn file included from ../../perl.h:7935,                 from DynaLoader.xs:136:../../sv_inline.h: In function ‘S_uiv_2buf’:../../sv_inline.h:1066:14: warning: implicit declaration of function ‘NEGATE_2U’ [-Wimplicit-function-declaration] 1066 |         uv = NEGATE_2UV(iv);      |              ^~~~~~~~~~rm -rf ../../DynaLoader.o

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I'm confused, this code already usesTYPE_CHARS which is defined inhandy.h.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

NEGATE_2U is within an#ifdef PERL_CORE section, but DynaLoader only defines#define PERL_EXT. I'll take a look at it later.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I relaxed the #ifdef aroundNEGATE_2U. Shout if this seems inappropriate.

@tonycoz
Copy link
Contributor

Since this is an optimization, could you add a benchmark that exercises this tot/perf/benchmarks?

richardleach reacted with thumbs up emoji

@richardleachrichardleachforce-pushed thehydahy/iv2buf branch 2 times, most recently fromd2bdba3 to573b8f7CompareJanuary 25, 2025 23:23
@richardleach
Copy link
ContributorAuthor

Since this is an optimization, could you add a benchmark that exercises this tot/perf/benchmarks?

In the end, I couldn't see how to do anything portable since the benchmarks have to run underminiperl. Open to suggestions!

@bulk88
Copy link
Contributor

@richardleach
Move S_uiv_2buf from sv.c to sv_inline.h

S_uiv_2buf() should really be made public API, I have an unfinished branch to do that but you picked up the idea first.

itoa() is MS only, svcatpvf is okay, locale mutex locking, makes atleast on windows, the OSs print fmt string librarys slower and slower over the years. So there isnt any portable fast, num 2 ascii func in the perl api. but sv_2pv_flags is hiding its S_uiv_2buf() from core and cpan

Copy link
Contributor

@bulk88bulk88 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This entire commit (but not anything else on this branch)is a very bad idea, dozens/100s of unique addressedstatic const union { char arr[200]; U16 dummy;} int2str_table will be made in libperl.so/.dll and in CPAN XS modules. Just make it a normal exported function.

If you want leave the S_*() inner function in sv.c and rest of core uses the extern symbol. Or to get around ELFs sym tab bloat keep 1 wrapper as exported for cpan, then a 2nd or middle end for visibility hidden, ony inside libperl.

Whether LTO does or doesn't optimize it in some way inside 1 binary is upto the CC unless some one visibly identifies a code gen flaw and a b4/after explanation. This fn has 5 args, its too big in my armchair opinion to ever get inlined (ran out of c stack CPU registers/now spilling to c stack in ram). Its ultimately upto the CC, but per C spec, you will get 10s of unique copies of those global vars.

@bulk88
Copy link
Contributor

    if (SvTYPE(sv) == SVt_IV && SvIOK(sv)) {        assert(!SvGMAGICAL(sv));        if (SvIsUV(sv))            PerlIO_printf(fp, "%" UVuf, (UV)SvUVX(sv));        else            PerlIO_printf(fp, "%" IVdf, (IV)SvIVX(sv));        return !PerlIO_error(fp);    }

(There are no code comments to say why it does this. Perhaps deliberately preserving the type for some reason?)

Paranoia about portability to trinary CPUs and 1s complement? Cray (???) was the last supported perl port with weirdness in electronics/fundamentals. IDK if ISO C ever allowed no sign bit CPUs/CCs, or the CRC/parity/ecc bit, so IV would 31 bits, UV is 32 bits. Sign extend/overflow lt gt are software emulated by CC then.

SvPVX macros have similar const type propagation since NC 2005ish, going on. The SvPVX code looks as if Perl 5 codebase is ready to move to C++ or JVM, since that was alot of work, well organized, for C/C++ features that don't exist/got withdrawn.

@richardleach
Copy link
ContributorAuthor

@richardleachMove S_uiv_2buf from sv.c to sv_inline.h

S_uiv_2buf() should really be made public API

I had thought about that, but wasn't sure how useful it would be. If no-one objects, I'm happy to put in back intosv.c and make it public.

@richardleach
Copy link
ContributorAuthor

This fn has 5 args, its too big in my armchair opinion to ever get inlined

Even on aDEBUGGING build, gcc quite happily inlines it in bothPerl_sv_2pv_flags andPerl_do_print. For example:

doio.c:2220:15: missed:   will not early inline: Perl_do_print/319->S_uiv_2buf/279, growth 27 exceeds --param early-inlining-insnsdoio.c:2220:15: optimized:  Inlined S_uiv_2buf/279 into Perl_do_print/319 which now has time 97.741547 and size 209, net change of -28.

@richardleach
Copy link
ContributorAuthor

Its ultimately upto the CC, but per C spec, you will get 10s of unique copies of those global vars.

Do you know what mainstream compilers do this? (gcc does not seem to. I only seeint2str_table insv.o anddoio.o.)

@tonycoz
Copy link
Contributor

Since this is an optimization, could you add a benchmark that exercises this tot/perf/benchmarks?

In the end, I couldn't see how to do anything portable since the benchmarks have to run underminiperl. Open to suggestions!

You can usePerlIO::scalar inminiperl now:

$ ./miniperl -e 'open my $fh, ">", \$x; print $fh 123; print ">$x<\n"'>123<

sv_inline.h Outdated
'9', '8', '9', '9'
}};

/* uiv_2buf() was originally a private routine in sv.c for use by
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I don't think performance here is so important that it needs to be inline.

Unlike new_SV() for example, we're unlikely to call this with constants (leading to further optimization opportunities) and non-indirect function call overhead isn't that high on modern architectures that use register calling conventions.

Even if we do want to inline it, since the parameters are unlikely to be constants, there's no optimization benefit that I can see to making the values in int2str_table visible to the inline site - it could just be a global table.

sv_inline.h Outdated
@@ -1003,6 +1003,90 @@ Perl_sv_setpv_freshbuf(pTHX_ SV *const sv)
return SvPVX(sv);
}

/* Perl_int2str_table: lookup table containing string representations
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

With the "E" flag this and the function should only be defined when PERL_EXT or PERL_CORE is defined.

sv_inline.h Outdated
assert(PTR2nat(ptr) % 2 == 0);
/* we are going to read/write two bytes at a time */
word_ptr = (U16*)ptr;
word_table = (U16*)Perl_int2str_table.arr;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

This was a problem in the original, but there word_table could be a const pointer which would eliminate the need to cast away the const here (though not the cast entirely)

@richardleachrichardleachforce-pushed thehydahy/iv2buf branch 2 times, most recently fromf26851b to8e40ecdCompareFebruary 4, 2025 23:41
@richardleach
Copy link
ContributorAuthor

I think all comments have been actioned in one form or another.

I want to benchmarkbefore vsinline vsPerl_ cases before adding any perldoc though. (It seems like the current form ofuiv_2buf is about 25% slower for bothdo_print andsv_2pv_flags compared with when it was an inline function, but I have to check if that was just noise or really the case.)

This allows NEGATE_2UV, NEGATE_2IV, and ABS_IV_MIN to be usedoutside of core.
`Perl_do_print`'s pre-existing method for stringification of an IV withinan SVt_IV involves creating a temporary SVt_PV, using `sv_vcatpvfn_flags`to do the stringification, then freeing the SVt_PV once the buffer hasbeen written out.This is considerably slower than using `uiv_2buf`, the helperfunction used by `sv_2pv_flags`. So this commit modifies `Perl_do_print`to use `uiv_2buf`.
@richardleach
Copy link
ContributorAuthor

I want to benchmarkbefore vsinline vsPerl_ cases before adding any perldoc though.

Unsurprisingly, it's not a good idea to accidentally compare a non-debugging build with a debugging one. ;)

What do the reviewers think of the current state of this PR?

@tonycoz
Copy link
Contributor

Since I like numbers, I ran the func::print::iv_stringify benchmark:

my $iter = @ARGV ? shift : 1_000_000;open(my $fh, ">", \$x) or die $!;for (1 .. $iter) {  print $fh 1,2,3,4,5,6,7,8,9,0;}

against the head of this branch78e2138:

             measure          avg          min          max       branch-misses    2084609.1    2035476.0    2461979.0            branches  766136412.6  765935946.0  766298653.0    context-switches          0.8          0.0          4.0      cpu-migrations          0.1          0.0          1.0              cycles  932577190.3  925676292.0  961842402.0        instructions 3131574466.0 3130633706.0 3132396274.0         page-faults       2662.3       2459.0       2691.0             seconds          0.4          0.0          0.2          task-clock        198.8        197.1        205.2

and against the parent of this topic branch atd774dbe:

             measure          avg          min          max       branch-misses    2036839.3    2030070.0    2115408.0            branches 2376329145.7 2376153615.0 2376575531.0    context-switches          3.5          0.0         22.0      cpu-migrations          0.2          0.0          2.0              cycles 4009251963.7 3989971009.0 4131263532.0        instructions 10472614072.2 10471780943.0 10473859922.0         page-faults       2681.6       2463.0       2693.0             seconds          1.7          0.0          0.9          task-clock        849.3        839.6        879.6

That's a noticable difference.

Code used to run the perf
#!/usr/bin/perluse v5.36;use Getopt::Long;my $count = 50;GetOptions("c=n", \$count);my %tot;my %min;my %max;for my $i ( 1 .. $count ) {  my $cmd = "perf stat @ARGV 2>&1";  #print "cmd: $cmd\n";  my @out = `$cmd`;  #print @out;exit;  for my $line (@out) {    if (my ($num, $what) =        $line =~ /^\s+(\d[\d,.]*)\s+(?:msec\s+)?([\w-]+)\s+/) {      $num =~ tr/,//d;      $tot{$what} += $num;      $min{$what} = $num if !exists $min{$what} || $num < $min{$what};      $max{$what} = $num if !exists $max{$what} || $num > $max{$what};    }  }}printf "%20s %12s %12s %12s\n", "measure", "avg", "min", "max";for my $what (sort keys %tot) {  printf "%20s %12.1f %12.1f %12.1f\n", $what, $tot{$what}/$count,    $min{$what}, $max{$what};}

Invoked like:

perf-collect ./perl ../func-print-iv_stringify.pl
richardleach reacted with thumbs up emoji

@richardleachrichardleach merged commit2ed88f4 intoPerl:bleadFeb 10, 2025
@richardleachrichardleach deleted the hydahy/iv2buf branchFebruary 10, 2025 22:01
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment
Reviewers

@khwilliamsonkhwilliamsonkhwilliamson left review comments

@maukemaukemauke left review comments

@xenuxenuxenu left review comments

@bulk88bulk88bulk88 left review comments

@tonycoztonycoztonycoz approved these changes

Assignees
No one assigned
Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

6 participants
@richardleach@tonycoz@bulk88@khwilliamson@mauke@xenu

[8]ページ先頭

©2009-2025 Movatter.jp