Skip to content

Vector implementation for arraycmp on P8/P9 - #7011

Merged
dsouzai merged 1 commit into
eclipse-omr:masterfrom
IBMJimmyk:vectorArrayCmp
Jan 13, 2025
Merged

Vector implementation for arraycmp on P8/P9#7011
dsouzai merged 1 commit into
eclipse-omr:masterfrom
IBMJimmyk:vectorArrayCmp

Conversation

@IBMJimmyk

Copy link
Copy Markdown
Contributor

WIP implementation for modifying P8/P9 arraycmp to use vector operations.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The new implementation will use lxvw4x to load 16 bytes from each array at a time to perform the comparison. This is okay on both big and little endian since at this point it only needs to know if there is a difference in the 16 bytes but does not yet need to know which byte position the difference is at. If the bytes are the same, it moves on to the next 16 bytes. If there is a difference, 16 bytes are reloaded one at a time via the residue loop to identify at which position the difference occurred at. If there are fewer than 16 bytes left to load, the residue loop is also used.

The current code looks like this:

------------------------------
 n8763n   (  0)  treetop                                                                              [    0x74e17b95a2d0] bci=[0,10,83] rc=0 vc=4277 vn=- li=10 udi=- nc=1
 n8762n   (  3)    arraycmp  <arraycmp>[#241  helper Method] [flags 0x400 0x0 ] (in GPR_0163) (arrayCmpLen )  [    0x74e17b95a280] bci=[0,10,83] rc=3 vc=4277 vn=- li=10 udi=35760 nc=3 flg=>
 n8749n   (  0)      aladd (in GPR_0160) (X>=0 internalPtr )                                          [    0x74e17b959e70] bci=[0,10,83] rc=0 vc=4277 vn=- li=10 udi=34944 nc=2 flg=0x8100
 n33n     (  0)        ==>l2a (in &GPR_0104) (X!=0 )
 n8751n   (  0)        lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )                              [    0x74e17b959f10] bci=[0,10,83] rc=0 vc=4277 vn=- li=10 udi=- nc=0 flg=0x5104
 n8755n   (  0)      aladd (in GPR_0161) (X>=0 internalPtr )                                          [    0x74e17b95a050] bci=[0,13,83] rc=0 vc=4277 vn=- li=10 udi=35216 nc=2 flg=0x8100
 n38n     (  0)        ==>l2a (in &GPR_0105)
 n8751n   (  0)        ==>lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )
 n8761n   (  0)      i2l (in GPR_0162) (highWordZero X>=0 )                                           [    0x74e17b95a230] bci=[0,4,82] rc=0 vc=4277 vn=- li=10 udi=35488 nc=1 flg=0x4100
 n411n    (  2)        ==>iloadi (in GPR_0112) (X>=0 cannotOverflow )
------------------------------

 [    0x74e0522588f0]   10      addi    GPR_0160, &GPR_0104, 8
 [    0x74e052258a00]   13      addi    GPR_0161, &GPR_0105, 8
 [    0x74e052258b10]   4       extsw   GPR_0162, GPR_0112
 [    0x74e052259540]   10      Label L0337:    ; (Start of internal control flow)
 [    0x74e0522595d0]   10      li      GPR_0163, 0000000000000000
 [    0x74e052259670]   10      cmpdi   CCR_0168, GPR_0162, 16
 [    0x74e052259710]   10      blt     CCR_0168, Label L0339
 [    0x74e0522597b0]   10      sradi   GPR_0164, GPR_0162, 4
 [    0x74e052259850]   10      mtctr   GPR_0164
 [    0x74e0522598f0]   10      Label L0338:
 [    0x74e052259980]   10      lxvw4x  VRF_0166, GPR_0163, GPR_0160
 [    0x74e052259a20]   10      lxvw4x  VRF_0167, GPR_0163, GPR_0161
 [    0x74e052259ac0]   10      vcmpequb.       VRF_0166, VRF_0166, VRF_0167
 [    0x74e052259b60]   10      bge     CCR_0168, Label L0340
 [    0x74e052259c00]   10      addi    GPR_0163, GPR_0163, 16
 [    0x74e052259ca0]   10      bdnz    CCR_0168, Label L0338
 [    0x74e052259d40]   10      Label L0339:
 [    0x74e052259dd0]   10      cmpd    CCR_0168, GPR_0163, GPR_0162
 [    0x74e052259e70]   10      beq     CCR_0168, Label L0341
 [    0x74e052259f10]   10      Label L0340:
 [    0x74e052259fa0]   10      lbzx    GPR_0164, GPR_0163, GPR_0160
 [    0x74e05225a040]   10      lbzx    GPR_0165, GPR_0163, GPR_0161
 [    0x74e05225a0e0]   10      cmpw    CCR_0168, GPR_0164, GPR_0165
 [    0x74e05225a180]   10      bne     CCR_0168, Label L0342
 [    0x74e05225a220]   10      addi    GPR_0163, GPR_0163, 1
 [    0x74e05225a2c0]   10      cmpd    CCR_0168, GPR_0163, GPR_0162
 [    0x74e05225a360]   10      bne     CCR_0168, Label L0340
 [    0x74e05225a400]   10      Label L0341:
 [    0x74e05225a490]   10      Label L0342:
 [    0x74e05225a520]   10      Label L0343:    ; (End of internal control flow)

I wrote a small test to perform arraycmp 100 million times on various array sizes.

Size Baseline (s) Vector (s) Diff
1 0.482 0.414 0.859
2 0.576 0.498 0.865
3 0.755 0.816 1.081
4 0.763 0.698 0.915
5 0.872 0.832 0.954
6 0.999 0.975 0.976
7 1.111 1.089 0.980
8 0.617 1.238 2.006
9 0.825 1.375 1.667
10 0.953 1.52 1.595
11 1.044 2.077 1.989
12 1.153 2.207 1.914
13 1.282 2.436 1.900
14 1.439 2.56 1.779
15 1.566 2.576 1.645
16 0.763 0.441 0.578
17 0.959 0.524 0.546
18 1.051 0.61 0.580
19 1.136 0.992 0.873
20 1.245 0.819 0.658
21 1.406 0.976 0.694
22 1.531 1.011 0.660
23 1.667 1.11 0.666
24 0.83 1.232 1.484
25 1.073 1.509 1.406
26 1.203 1.767 1.469
27 1.248 1.888 1.513
28 1.357 2.428 1.789
29 1.634 2.676 1.638
30 1.648 2.8 1.699
31 1.786 2.798 1.567
32 0.968 0.892 0.921

@zl-wang You mentioned you wanted to take a look at this.

@zl-wang

zl-wang commented May 29, 2023

Copy link
Copy Markdown
Contributor

that is too much performance difference. i think a few improvement should be pursued:

  1. a separate implementation for POWER9 which supports both lxvh8x/vcmpequh/vclzh and lxvb16x/vcmpequb/vclzb (although missing vector load with length ... i.e. still need a residue loop for the case of comparing equal in the vector portion)
  2. POWER8 also has vclz(h|b) to make it convenient for calculating vector comparison portion;
  3. residue loop can tailor to char or byte cases. not always using byte load, that is;

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

Okay, I'll try taking a look at seeing if those instructions can help.

@zl-wang

zl-wang commented May 29, 2023

Copy link
Copy Markdown
Contributor

for typical String comparison, if using char (2-byte) and residue iteration is going to be half of the current, i expected no case is going to be 2x slower.

@zl-wang

zl-wang commented May 29, 2023

Copy link
Copy Markdown
Contributor

actually, i projected most of the cases would be faster. only 8-byte case could be slower, but almost equal.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The new vector implementation looks like this:

startlabel:
  li       offsetReg, 0
  cmpdi    lengthReg, 8
  blt      skipLoad8Label
  li       tempReg, 0x77
  andc     tempReg, lengthReg, tempReg
  cmpdi    tempReg, 8
  beq      skipLoad16Label
  sradi    tempReg, lengthReg, 4
  mtctr    tempReg
load16LoopLabel:
  lxvw4x   vec1Reg, offsetReg, src1AddrReg   // Can change to lxvb16x on Power 9
  lxvw4x   vec2Reg, offsetReg, src2AddrReg   // Can change to lxvb16x on Power 9
  vcmpequb vec1Reg, vec1Reg, vec2Reg
  bge      cr6, vectorDiffLabel
  addi     offsetReg, offsetReg, 16
  bdnz     load16LoopLabel
skipLoad16Label:
  subf     tempReg, offsetReg, lengthReg
  cmpdi    tempReg, 8
  blt      skipLoad8Label
  sradi    tempReg, tempReg, 3
  mtctr    tempReg
load8LoopLabel:
  ldx      tempReg, offsetReg, src1AddrReg
  ldx      tempReg2, offsetReg, src2AddrReg
  cmpd     tempReg, tempReg2
  bne      residueLoopLabel
  addi     offsetReg, offsetReg, 8
  bdnz     load8LoopLabel
skipLoad8Label:
  cmpd     offsetReg, lengthReg
  beq      returnTrueLabel
residueLoopLabel:
  lbzx     tempReg, offsetReg, src1AddrReg
  lbzx     tempReg2, offsetReg, src2AddrReg
  cmpw     tempReg, tempReg2
  bne      returnFalseLabel
  addi     offsetReg, offsetReg, 1
  cmpd     offsetReg, lengthReg
  bne      residueLoopLabel
  b        returnTrueLabel
vectorDiffLabel:
#if (ppcle)
  vspltisw vec2Reg, -16               /*
  vrlw     vec1Reg, vec1Reg, vec2Reg   * These 4 won't be needed on Power 9.
  vspltish vec2Reg, 8                  *
  vrlh     vec1Reg, vec1Reg, vec2Reg   */
#endif
  vspltisw vec2Reg, -1
  vxor     vec1Reg, vec1Reg, vec2Reg
  mfvsrd   tempReg, vec1Reg
  cntlzd   tempReg, tempReg
  sradi    tempReg, tempReg, 3
  add      offsetReg, offsetReg, tempReg
  cmpdi    tempReg, 8
  bne      returnVectorFalseLabel
  xxpermdi vec1Reg, vec1Reg, vec1Reg, 3  /*  use mfvsrld on Power 9
  mfvsrd   tempReg, vec1Reg               */
  cntlzd   tempReg, tempReg
  sradi    tempReg, tempReg, 3
  add      offsetReg, offsetReg, tempReg
  b        returnVectorFalseLabel
returnTrueLabel:
returnVectorFalseLabel:
returnFalseLabel:

One change from before is to avoid a long residue loop when there are fewer than 16 elements left. This is done in two way. First off for arrays smaller than 128, array size is between 16x+8 and 16x+15 where x is an integers, it will fall back to using 8 byte loads. The second is after the vector loop, if there are still 8 or more bytes left there will be one loop of loading with an 8 byte load before doing byte by byte loads. It recently occurred to me that doing both might be too much so it might be possible to remove the first case but I haven't tried this yet.

The second change is an attempt at better handling of finding a mismatched byte while in the vector portion of the loop. A path was added to reorder the bytes under little endian to match how they are in the array. The bytes are then copied to GPRs where cntlzd is used to locate the mismatched byte and adjust offsetReg to indicate where in the array this is. This section is still a bit of a mess and I'm trying to figure out if it can be done better than it is right now.

I got updated performance data.

To make the run a bit longer, I increased the number of interations to 1 billion.
These runs were done on the perf machine cit926.

This table covers the cases where there is a complete match on arrays of different lengths:

Length Baseline Vector Diff%
1 3.633 3.249 11.8%
2 4.491 4.675 -3.9%
3 5.87 5 17.4%
4 5.82 6.099 -4.6%
5 6.637 7.206 -7.9%
6 7.721 8.232 -6.2%
7 8.572 9.124 -6%
8 4.69 3.953 18.6%
9 6.446 4.772 35.1%
10 7.27 6.173 17.8%
11 7.984 6.462 23.6%
12 8.831 7.151 23.5%
13 9.843 8.013 22.8%
14 11.05 8.978 23.1%
15 12.167 9.774 24.5%
16 5.503 5.467 0.7%
17 7.475 6.091 22.7%
18 8.232 7.221 14%
19 8.69 7.6 14.3%
20 9.513 8.189 16.2%
21 11.04 9.002 22.6%
22 11.842 9.91 19.5%
23 12.787 10.846 17.9%
24 6.48 5.489 18.1%
25 8.083 6.388 26.5%
26 8.897 7.263 22.5%
27 9.58 7.967 20.2%
28 10.305 8.927 15.4%
29 12.409 9.793 26.7%
30 12.511 10.608 17.9%
31 13.586 11.497 18.2%
32 7.426 6.295 18%
128 20.77 13.458 54.3%
1000 114.729 80.266 42.9%

Somewhere along the way my implementation got worse for smaller arrays (size 2, 4-7). I used to get better numbers. I think it might have happened when I was making changes for better handing on an array mismatch which was unexpected. I will need to look into this a bit further. For other array sizes up to 32, my new version works better. I also showed the 128 and 1000 length cases to show it works better for larger arrays as well.

The following data is for the case where there a mismatched byte in the two arrays being compared.
There is a length parameter that is the length of the array and also this index of the mismatched element. This goes from 0 to length-1. So for a 7 length array it goes from 0-6.

Different array lengths take different paths through the code.
7 length array - goes stright to the byte by byte residue loop.
15 length array - uses an 8 byte load for the first 8 byte then goes to the byte by byte residue loop. If the mismatch is in the first 8 bytes, it goes to the residue loop as well.
23 length array - uses a 16 byte vector load for the first 16 bytes then goes to the byte by byte residue loop. If the mismatch is in the first 16 bytes, the new vector handing of finding the location of the mismatch is used.

Length Mismatch Idx Baseline Vector Diff%
7 0 4.486 4.332 3.6%
7 1 6.201 6.68 -7.2%
7 2 6.161 6.115 0.8%
7 3 7.123 7.353 -3.1%
7 4 7.841 8.269 -5.2%
7 5 8.767 9.269 -5.4%
7 6 9.886 10.233 -3.4%
15 0 6.365 5.424 17.3%
15 1 7.322 6.505 12.6%
15 2 7.959 7.4 7.6%
15 3 9.124 8.398 8.6%
15 4 10.08 9.591 5.1%
15 5 10.92 10.976 -0.5%
15 6 12.21 11.507 6.1%
15 7 13.002 12.491 4.1%
15 8 6.314 5.048 25.1%
15 9 7.27 5.959 22%
15 10 7.963 6.937 14.8%
15 11 8.908 8.001 11.3%
15 12 10.909 9.838 10.9%
15 13 11.02 10.657 3.4%
15 14 12.222 11.155 9.6%
23 0 6.34 6.94 -8.6%
23 1 7.326 6.949 5.4%
23 2 8.165 6.942 17.6%
23 3 8.973 6.942 29.3%
23 4 10.072 6.83 47.5%
23 5 10.997 6.957 58.1%
23 6 12.213 6.964 75.4%
23 7 13.152 6.949 89.3%
23 8 7.964 7.281 9.4%
23 9 8.082 7.255 11.4%
23 10 9.035 7.387 22.3%
23 11 9.742 7.284 33.7%
23 12 10.75 7.388 45.5%
23 13 11.801 7.227 63.3%
23 14 12.997 6.902 88.3%
23 15 14.235 7.241 96.6%
23 16 7.505 6.168 21.7%
23 17 8.217 6.805 20.7%
23 18 8.787 8.113 8.3%
23 19 9.59 9.115 5.2%
23 20 10.65 10.106 5.4%
23 21 11.753 11.605 1.3%
23 22 13.821 11.571 19.4%

The worst case is the 23 length vector where the mismatch is on the very first byte. This takes the path of going to the vector implementation of finding the mismatched byte. But, this is worse than the byte by byte residue loop. The reason for that is this is the best case for the residue loop as it finds a hit on the very first byte it tries to compare. In general this will also happen for larger arrays as well where it goes to the vector path and the mismatch is on the very first byte.

@zl-wang

zl-wang commented Jun 2, 2023

Copy link
Copy Markdown
Contributor

i felt intuitively it can be improved possibly in the following areas:

  1. I don't see you need 8 iterations of vector loop to recover the overhead for going to load8Loop ... i.e. testing for andc 0x77. testing for bigger than/equal to 16 might be good;
  2. in load8Loop, for mismatch cases, we can certainly use cmpb instruction to generate a similar mask result, feeding naturally to the final result calculation;
  3. for residue, as long as it is equal-to or more-than 4 bytes, we can use lwzx instruction to create similar to 8-byte situation. i hoped this reduction in the final lbzx number will help the overall picture;

i haven't looked at the vectorDiffLabel code carefully yet to see possible improvements. i might come back to this later.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The performance regression mentioned in the earlier post was caused by an unconditional branch to branch around newly added assembly to handle finding mismatched bytes during the vector load section. I rearranged the code to reduce the effects.

Using the count register will slightly reduce the number of instructions in the residue loop. I had initially thought it wouldn't due the additional instructions to set up the count registers but it worked out when I tried it.

Removed the 0x77 check. This was a more complicated way to select between using vector loads or 8 byte loads to avoid longer byte by byte residue loop. However, residue handling has since changed and this check is no longer needed.

Added support for the cmpb instruction. This make it possible to find the mismatching bytes during the 8 byte load section in a similar way to the vector load section.

Added support for a 4 byte load section. If there are 4 to 7 bytes left to compare, this path is now taken rather than going straight to the byte loop.

Removed looping on the 4 and 8 byte load sections. I realized that these code paths will never loop. The 4 byte load section is only used when there are 7 or fewer bytes left to compare. Similarly, the 8 byte load section is only used when there are 15 of fewer bytes left. As a result, I was able to remove a few extra instructions that were previously used to handle the 4 or 8 byte load sections looping.

The remove looping was only recently added so I don't have updated performance numbers for that case yet.

Without that last change, the current worst case is when there is a first byte mismatch on an array of size 4, 5, 6 or 7.
Under this scenario, the old implementation will not try to use an 8 byte load since the array is too short. It will go to byte loop and find a hit on the very first byte. This is essentially the best case scenario for the old implementation.
The new implementation will try to use a 4 byte load and discover there is a mismatch in one of the 4 bytes loaded. It will then use cmpb and some data movement to locate the mismatch. This turns out to be about 16% slower. There are a few other cases that are 3% slower and all other cases are faster.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The newest change adds an earlier length check against 4 after the vector load section. This will skip past both the 8 byte and 4 byte load sections instead of just skipping over one at a time.

The latest implementation looks like this:

startlabel:
  li       offsetReg, 0
  cmpdi    cr6, lengthReg, 4
  blt      cr6, skipLoad4Label
  cmpdi    cr6, lengthReg, 8
  blt      cr6, skipLoad8Label
  cmpdi    cr6, lengthReg, 16
  blt      cr6, skipLoad16Label
  sradi    tempReg, lengthReg, 4
  mtctr    tempReg
load16LoopLabel:
  lxvw4x   vec1Reg, offsetReg, src1AddrReg
  lxvw4x   vec2Reg, offsetReg, src2AddrReg
  vcmpequb vec1Reg, vec1Reg, vec2Reg
  bge      cr6, load16DiffLabel
  addi     offsetReg, offsetReg, 16
  bdnz     load16LoopLabel
  b        doneLoad16Label
load16DiffLabel:
#if (ppcle)
  vspltisw vec2Reg, -16
  vrlw     vec1Reg, vec1Reg, vec2Reg
  vspltish vec2Reg, 8
  vrlh     vec1Reg, vec1Reg, vec2Reg
#endif
  mfvsrd   tempReg, vec1Reg
  nor      tempReg, tempReg, tempReg
  cntlzd   tempReg, tempReg
  sradi    tempReg, tempReg, 3
  add      offsetReg, offsetReg, tempReg
  cmpdi    cr6, tempReg, 8
  bne      cr6, returnVectorFalseLabel
  xxpermdi vec1Reg, vec1Reg, vec1Reg, 3  /*  use mfvsrld for Power 9
  mfvsrd   tempReg, vec1Reg               */
  nor      tempReg, tempReg, tempReg
  cntlzd   tempReg, tempReg
  sradi    tempReg, tempReg, 3
  add      offsetReg, offsetReg, tempReg
  b        returnVectorFalseLabel
load4DiffLabel:
  addi     offsetReg, offsetReg, -4
load8DiffLabel:
  cmpb     tempReg, tempReg, tempReg2
  nor      tempReg, tempReg, tempReg
  cntlzd   tempReg, tempReg
  sradi    tempReg, tempReg, 3
  add      offsetReg, offsetReg, tempReg
  b        returnVectorFalseLabel
doneLoad16Label:
  subf.    tempReg, offsetReg, lengthReg
  cmpdi    cr6, tempReg, 4
  blt      cr6, skipLoad4Label2
  cmpdi    cr6, tempReg, 8
  blt      cr6, skipLoad8Label2
skipLoad16Label:
  ldbrx    tempReg, offsetReg, src1AddrReg    //ldx for big endian
  ldbrx    tempReg2, offsetReg, src2AddrReg   //ldx for big endian
  cmpd     cr6, tempReg, tempReg2
  bne      cr6, load8DiffLabel
  addi     offsetReg, offsetReg, 8
  subf.    tempReg, offsetReg, lengthReg
skipLoad8Label2:
  cmpdi    cr6, tempReg, 4
  blt      cr6, skipLoad4Label2
skipLoad8Label:
  lwbrx    tempReg, offsetReg, src1AddrReg    //lwx for big endian
  lwbrx    tempReg2, offsetReg, src2AddrReg   //lwx for big endian
  cmpw     cr6, tempReg, tempReg2
  bne      cr6, load4DiffLabel
  addi     offsetReg, offsetReg, 4
skipLoad4Label:
  subf.    tempReg, offsetReg, lengthReg
skipLoad4Label2:
  beq      cr0, returnTrueLabel
  mtctr    tempReg
residueLoopLabel:
  lbzx     tempReg, offsetReg, src1AddrReg
  lbzx     tempReg2, offsetReg, src2AddrReg
  cmpw     cr6, tempReg, tempReg2
  bne      cr6, returnFalseLabel
  addi     offsetReg, offsetReg, 1
  bdnz     residueLoopLabel
returnTrueLabel:
returnVectorFalseLabel:
returnFalseLabel:

There is a vector 16 byte load section followed by an 8 byte load, 4 byte load then the residue loop. If a mismatch is found in the vector section, it will jump to load16DiffLabel to locate where in the vector register the difference occured. If it happens in the 8 byte or 4 byte load sections, cmpb is used to locate the difference.

I also updated the performance data. The number of interations to 1 billion which is the same as the previous tests.
These runs were done on the perf machine cit926.
I added more test cases so I had to retake the baseline.

This table covers the cases where there is a complete match on arrays of different lengths:

Length Baseline (s) Vector (s) Diff%
1 3.63 3.092 17.4%
2 4.366 3.783 15.4%
3 5.749 5.85 -1.7%
4 5.815 3.24 79.5%
5 6.64 3.835 73.1%
6 7.725 4.374 76.6%
7 8.441 5.079 66.2%
8 4.818 3.879 24.2%
9 6.442 4.505 43%
10 7.281 6.936 5%
11 7.986 6.004 33%
12 8.806 4.494 96%
13 9.832 4.938 99.1%
14 11.046 7.333 50.6%
15 12.158 7.146 70.1%
16 5.619 5.331 5.4%
17 7.335 5.763 27.3%
18 8.231 8.484 -3%
19 8.819 7.057 25%
20 9.652 6.163 56.6%
21 11.239 6.578 70.9%
22 11.831 7.154 65.4%
23 12.893 7.791 65.5%
24 6.328 6.174 2.5%
25 8.104 6.588 23%
26 8.764 7.067 24%
27 9.592 7.788 23.2%
28 10.417 6.834 52.4%
29 12.392 7.103 74.5%
30 12.51 7.712 62.2%
31 13.752 8.205 67.6%
32 7.3 7.601 -4%
39 14.338 9.408 52.4%
40 8.4 8.143 3.2%
47 15.142 9.624 57.3%
48 9.13 8.593 6.3%
55 16.064 10.823 48.4%
56 9.971 8.793 13.4%
63 16.738 11.065 51.3%
64 10.572 9.557 10.6%
71 17.731 11.724 51.2%
72 11.375 10.055 13.1%
79 18.525 12.027 54%
80 12.348 10.927 13%
87 19.244 13.025 47.8%
88 13.072 11.923 9.6%
95 20.238 13.07 54.8%
96 17.385 11.561 50.4%
103 23.483 13.707 71.3%
104 19.293 12.12 59.2%
111 24.383 14.136 72.5%
112 20.071 13.21 51.9%
119 25.211 15.599 61.6%
120 19.999 13.516 48%
127 26.104 15.686 66.4%
128 20.762 13.873 49.7%
1000 114.716 82.653 38.8%

The vector implementation helps in most case. The worst case seems to be 4% down on a 32 byte array. That path uses two vector loads to handle the entire match so I'm not entirely sure why it seems to be worse.

The following data is for the case where there a mismatched byte in the two arrays being compared.
There is a length parameter that is the length of the array and also this index of the mismatched element. This goes from 0 to length-1. So for a 7 length array it goes from 0-6.

Different array lengths take different paths through the code.
3 length array - goes stright to the byte by byte residue loop.
7 length array - uses a 4 byte load for the first 4 bytes then goes to the byte by byte residue loop.
11 length array - uses an 8 byte load for the first 8 byte then goes to the byte by byte residue loop.
15 length array - uses and 8 byte load followed by an 4 byte load then the residue loop
19 length array - uses a 16 byte vector load for the first 16 bytes then goes to the byte by byte residue loop.
23 length array - uses 16 byte load then 4 byte load
27 length array - uses 16 byte load then 8 byte load
31 length array - uses 16 byte load then 8 byte load then 4 byte load

Length Mismatch Idx Baseline (s) Vector (s) Diff%
3 0 4.604 4.492 2.5%
3 1 6.14 5.326 15.3%
3 2 6.112 6.063 0.8%
7 0 4.591 5.263 -12.8%
7 1 6.303 5.262 19.8%
7 2 6.109 5.356 14.1%
7 3 6.951 5.267 32%
7 4 7.859 4.48 75.4%
7 5 8.775 5.35 64%
7 6 9.851 6.211 58.6%
11 0 6.285 5.56 13%
11 1 7.248 5.567 30.2%
11 2 7.964 5.465 45.7%
11 3 8.922 5.472 63.1%
11 4 9.849 5.565 77%
11 5 10.992 5.565 97.5%
11 6 11.99 5.47 119.2%
11 7 13.029 5.57 133.9%
11 8 6.324 5.498 15%
11 9 7.397 6.376 16%
11 10 7.924 7.11 11.5%
15 0 6.164 5.468 12.7%
15 1 7.102 5.561 27.7%
15 2 8.079 5.566 45.2%
15 3 8.905 5.469 62.8%
15 4 9.974 5.571 79%
15 5 10.916 5.47 99.6%
15 6 11.987 5.468 119.2%
15 7 12.911 5.466 136.2%
15 8 6.459 5.761 12.1%
15 9 7.255 5.762 25.9%
15 10 8.049 5.762 39.7%
15 11 8.815 5.762 53%
15 12 10.926 5.353 104.1%
15 13 10.96 5.997 82.8%
15 14 12.133 6.993 73.5%
19 0 6.146 6.476 -5.1%
19 1 7.108 6.517 9.1%
19 2 8.081 6.461 25.1%
19 3 8.902 6.585 35.2%
19 4 9.975 6.587 51.4%
19 5 10.992 6.585 66.9%
19 6 11.987 6.586 82%
19 7 12.918 6.589 96.1%
19 8 7.762 7.291 6.5%
19 9 7.95 7.286 9.1%
19 10 8.842 7.158 23.5%
19 11 9.879 7.297 35.4%
19 12 10.8 7.159 50.9%
19 13 11.839 7.286 62.5%
19 14 12.864 7.283 76.6%
19 15 14.26 7.157 99.3%
19 16 7.297 6.589 10.8%
19 17 7.975 7.126 11.9%
19 18 8.665 8.019 8.1%
23 0 6.286 6.478 -3%
23 1 7.117 6.477 9.9%
23 2 7.943 6.515 21.9%
23 3 9.022 6.586 37%
23 4 9.873 6.588 49.9%
23 5 10.913 6.586 65.7%
23 6 11.962 6.612 80.9%
23 7 13.029 6.608 97.2%
23 8 7.902 7.284 8.5%
23 9 8.084 7.3 10.7%
23 10 8.846 7.281 21.5%
23 11 9.694 7.156 35.5%
23 12 10.674 7.175 48.8%
23 13 11.721 7.16 63.7%
23 14 12.727 7.158 77.8%
23 15 14.258 7.159 99.2%
23 16 7.292 7.599 -4%
23 17 8.128 7.604 6.9%
23 18 8.673 7.47 16.1%
23 19 9.644 7.473 29.1%
23 20 10.715 7.134 50.2%
23 21 11.812 7.786 51.7%
23 22 13.697 8.613 59%
27 0 6.188 6.11 1.3%
27 1 7.129 6.109 16.7%
27 2 7.958 6.176 28.9%
27 3 8.949 6.226 43.7%
27 4 9.896 6.111 61.9%
27 5 10.946 6.1 79.4%
27 6 12.127 6.246 94.2%
27 7 13.051 6.231 109.5%
27 8 7.834 6.962 12.5%
27 9 7.97 6.977 14.2%
27 10 8.842 6.971 26.8%
27 11 9.717 6.967 39.5%
27 12 10.816 6.964 55.3%
27 13 11.752 6.806 72.7%
27 14 12.882 6.968 84.9%
27 15 14.16 6.975 103%
27 16 8.149 6.83 19.3%
27 17 9.743 6.699 45.4%
27 18 9.682 6.834 41.7%
27 19 10.714 6.843 56.6%
27 20 11.656 6.69 74.2%
27 21 12.666 6.693 89.2%
27 22 14.128 6.712 110.5%
27 23 15.739 6.747 133.3%
27 24 8.021 6.828 17.5%
27 25 8.714 7.761 12.3%
27 26 9.664 8.495 13.8%
31 0 6.31 6.187 2%
31 1 7.145 6.232 14.7%
31 2 7.972 6.233 27.9%
31 3 9.053 6.197 46.1%
31 4 9.875 6.095 62%
31 5 11.029 6.237 76.8%
31 6 12.008 6.112 96.5%
31 7 13.05 6.188 110.9%
31 8 7.834 6.809 15.1%
31 9 8.099 6.962 16.3%
31 10 8.863 6.808 30.2%
31 11 9.884 6.811 45.1%
31 12 10.819 6.809 58.9%
31 13 11.72 6.81 72.1%
31 14 12.883 6.964 85%
31 15 14.167 6.964 103.4%
31 16 7.996 6.693 19.5%
31 17 9.832 6.709 46.6%
31 18 9.875 6.836 44.5%
31 19 10.714 6.833 56.8%
31 20 11.559 6.829 69.3%
31 21 12.667 6.835 85.3%
31 22 14.126 6.831 106.8%
31 23 15.632 6.71 133%
31 24 8.022 7.586 5.8%
31 25 8.862 7.411 19.6%
31 26 9.656 7.408 30.4%
31 27 10.475 7.574 38.3%
31 28 11.621 7.255 60.2%
31 29 12.8 7.872 62.6%
31 30 14.03 8.483 65.4%

The big problem here is being 13% slower when the array is 7 bytes long and the mismatch is on the very first byte. Under the old implementation, it would go straight to the residue loop and get a hit on the first compare. Under the new implementation it attempts to use a 4 byte load, finds a mismatch then takes a path to find the mismatch within the 4 bytes. The next biggest problem is being 5% down on a 19 byte array when there is a mismatch on the first byte. This one needs to do the vector mismatch path to find a mismatch on the first byte.

@zl-wang

zl-wang commented Jun 12, 2023

Copy link
Copy Markdown
Contributor

i don't see it is necessary to have skipLoad8Label2, since the only use of it is after comparing less than 4 already.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

That looks right to me. Instead of jumping to skipLoad8Label2 it can go to skipLoad8Label since it is known that there are 4-7 bytes left at that point.

@zl-wang

zl-wang commented Jun 12, 2023

Copy link
Copy Markdown
Contributor

using vnor and vclz instructions to calculate which byte it mismatches seems faster to me.

@zl-wang

zl-wang commented Jun 12, 2023

Copy link
Copy Markdown
Contributor

you should evaluate the alternative no-loop residue code (at most 3 pairs of load and cmp), because, on P8, mtctr is still relatively expensive (5 cycles i remembered). P9 is even worse in that regard.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I don't see a vclz instruction. I see vclzd which counts leading zeros per doubleword element. But, I think that might still work out a little better anyways. I'll need to check. vnor should work better than the two nor instructions so I can change that.

I can also check to see if I can remove the loop on the residue section.

@zl-wang

zl-wang commented Jun 12, 2023

Copy link
Copy Markdown
Contributor

on P9, instead of loading vector of 4 words, you should use loading vector of 16 bytes (such that there is no diff between LE and BE, and no need to rotate the mask result).

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

Oh, I had that one planned but forgot to mark it down. I will be using that and removing some of the instructions to rearrange the data on Power 9.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I was seeing unusual performance numbers for the test case of comparing 2 identical arrays that were both 32 bytes long. The bad case was about 30% slower.

This is case that has good performance:

------------------------------
 n8763n   (  0)  treetop                                                                              [    0x794abe9ea2d0] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=- nc=1
 n8762n   (  3)    arraycmp  <arraycmp>[#241  helper Method] [flags 0x400 0x0 ] (in GPR_0163) (arrayCmpLen )  [    0x794abe9ea280] bci=[0,10,106] rc=3 vc=4277 vn=- li=10 udi=35760 nc=3 flg=
0x8020
 n8749n   (  0)      aladd (in GPR_0160) (X>=0 internalPtr )                                          [    0x794abe9e9e70] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=34944 nc=2 flg=0x8100
 n33n     (  0)        ==>l2a (in &GPR_0104) (X!=0 )
 n8751n   (  0)        lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )                              [    0x794abe9e9f10] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=- nc=0 flg=0x5104
 n8755n   (  0)      aladd (in GPR_0161) (X>=0 internalPtr )                                          [    0x794abe9ea050] bci=[0,13,106] rc=0 vc=4277 vn=- li=10 udi=35216 nc=2 flg=0x8100
 n38n     (  0)        ==>l2a (in &GPR_0105)
 n8751n   (  0)        ==>lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )
 n8761n   (  0)      i2l (in GPR_0162) (highWordZero X>=0 )                                           [    0x794abe9ea230] bci=[0,4,105] rc=0 vc=4277 vn=- li=10 udi=35488 nc=1 flg=0x4100
 n411n    (  2)        ==>iloadi (in GPR_0112) (X>=0 cannotOverflow )
------------------------------

 [    0x794a1cfc88f0]   10      addi    GPR_0160, &GPR_0104, 8
 [    0x794a1cfc8a00]   13      addi    GPR_0161, &GPR_0105, 8
 [    0x794a1cfc8b10]   4       extsw   GPR_0162, GPR_0112
 [    0x794a1cfc9970]   10      Label L0337:    ; (Start of internal control flow)
 [    0x794a1cfc9a00]   10      li      GPR_0163, 0000000000000000
 [    0x794a1cfc9aa0]   10      cmpdi   CCR_0169, GPR_0162, 4
 [    0x794a1cfc9b40]   10      blt     CCR_0169, Label L0346
 [    0x794a1cfc9be0]   10      cmpdi   CCR_0169, GPR_0162, 8
 [    0x794a1cfc9c80]   10      blt     CCR_0169, Label L0345
 [    0x794a1cfc9d20]   10      cmpdi   CCR_0169, GPR_0162, 16
 [    0x794a1cfc9dc0]   10      blt     CCR_0169, Label L0343
 [    0x794a1cfc9e60]   10      sradi   GPR_0164, GPR_0162, 4
 [    0x794a1cfc9f00]   10      mtctr   GPR_0164
 [    0x794a1cfc9fa0]   10      Label L0338:
 [    0x794a1cfca030]   10      lxvw4x  VRF_0166, GPR_0163, GPR_0160
 [    0x794a1cfca0d0]   10      lxvw4x  VRF_0167, GPR_0163, GPR_0161
 [    0x794a1cfca170]   10      vcmpequb.       VRF_0166, VRF_0166, VRF_0167
 [    0x794a1cfca210]   10      bge     CCR_0169, Label L0339
 [    0x794a1cfca2b0]   10      addi    GPR_0163, GPR_0163, 16
 [    0x794a1cfca350]   10      bdnz    CCR_0169, Label L0338
 [    0x794a1cfca3f0]   10      b       Label L0342
 [    0x794a1cfca480]   10      Label L0339:
 [    0x794a1cfca510]   10      vspltisw        VRF_0167, FFFFFFFFFFFFFFF0
 [    0x794a1cfca5b0]   10      vrlw    VRF_0166, VRF_0166, VRF_0167
 [    0x794a1cfca650]   10      vspltish        VRF_0167, 0000000000000008
 [    0x794a1cfca6f0]   10      vrlh    VRF_0166, VRF_0166, VRF_0167
 [    0x794a1cfca790]   10      mfvsrd  GPR_0164, VRF_0166
 [    0x794a1cfca830]   10      nor     GPR_0164, GPR_0164, GPR_0164
 [    0x794a1cfca8d0]   10      cntlzd  GPR_0164, GPR_0164
 [    0x794a1cfca970]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x794a1cfcaa10]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x794a1cfcaab0]   10      cmpdi   CCR_0169, GPR_0164, 8
 [    0x794a1cfcab50]   10      bne     CCR_0169, Label L0350
 [    0x794a1cfcabf0]   10      xxpermdi        VRF_0166, VRF_0166, VRF_0166, 0000000000000003
 [    0x794a1cfcaca0]   10      mfvsrd  GPR_0164, VRF_0166
 [    0x794a1cfcad40]   10      nor     GPR_0164, GPR_0164, GPR_0164
 [    0x794a1cfcade0]   10      cntlzd  GPR_0164, GPR_0164
 [    0x794a1cfcae80]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x794a1cfcaf20]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x794a1cfcafc0]   10      b       Label L0350
 [    0x794a1cfcb050]   10      Label L0340:
 [    0x794a1cfcb0e0]   10      addi    GPR_0163, GPR_0163, -4
 [    0x794a1cfcb180]   10      Label L0341:
 [    0x794a1cfcb210]   10      cmpb    GPR_0164, GPR_0164, GPR_0165
 [    0x794a1cfcb2b0]   10      nor     GPR_0164, GPR_0164, GPR_0164
 [    0x794a1cfcb350]   10      cntlzd  GPR_0164, GPR_0164
 [    0x794a1cfcb3f0]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x794a1cfcb490]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x794a1cfcb530]   10      b       Label L0350
 [    0x794a1cfcb5c0]   10      Label L0342:
 [    0x794a1cfcb650]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x794a1cfcb6f0]   10      cmpdi   CCR_0169, GPR_0164, 4
 [    0x794a1cfcb790]   10      blt     CCR_0169, Label L0347
 [    0x794a1cfcb830]   10      cmpdi   CCR_0169, GPR_0164, 8
 [    0x794a1cfcb8d0]   10      blt     CCR_0169, Label L0344
 [    0x794a1cfcb970]   10      Label L0343:
 [    0x794a1cfcba00]   10      ldbrx   GPR_0164, GPR_0163, GPR_0160
 [    0x794a1cfcbaa0]   10      ldbrx   GPR_0165, GPR_0163, GPR_0161
 [    0x794a1cfcbb40]   10      cmpd    CCR_0169, GPR_0164, GPR_0165
 [    0x794a1cfcbbe0]   10      bne     CCR_0169, Label L0341
 [    0x794a1cfcbc80]   10      addi    GPR_0163, GPR_0163, 8
 [    0x794a1cfcbd20]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x794a1cfcbdc0]   10      Label L0344:
 [    0x794a1cfcbe50]   10      cmpdi   CCR_0169, GPR_0164, 4
 [    0x794a1cfcbef0]   10      blt     CCR_0169, Label L0347
 [    0x794a1cfcbf90]   10      Label L0345:
 [    0x794a1cfcc020]   10      lwbrx   GPR_0164, GPR_0163, GPR_0160
 [    0x794a1cfcc0c0]   10      lwbrx   GPR_0165, GPR_0163, GPR_0161
 [    0x794a1cfcc160]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x794a1cfcc200]   10      bne     CCR_0169, Label L0340
 [    0x794a1cfcc2a0]   10      addi    GPR_0163, GPR_0163, 4
 [    0x794a1cfcc340]   10      Label L0346:
 [    0x794a1cfcc3d0]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x794a1cfcc470]   10      Label L0347:
 [    0x794a1cfcc500]   10      beq     CCR_0168, Label L0349
 [    0x794a1cfcc5a0]   10      mtctr   GPR_0164
 [    0x794a1cfcc640]   10      Label L0348:
 [    0x794a1cfcc6d0]   10      lbzx    GPR_0164, GPR_0163, GPR_0160
 [    0x794a1cfcc770]   10      lbzx    GPR_0165, GPR_0163, GPR_0161
 [    0x794a1cfcc810]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x794a1cfcc8b0]   10      bne     CCR_0169, Label L0351
 [    0x794a1cfcc950]   10      addi    GPR_0163, GPR_0163, 1
 [    0x794a1cfcc9f0]   10      bdnz    CCR_0169, Label L0348
 [    0x794a1cfcca90]   10      Label L0349:
 [    0x794a1cfccb20]   10      Label L0350:
 [    0x794a1cfccbb0]   10      Label L0351:
 [    0x794a1cfccc40]   10      Label L0352:    ; (End of internal control flow)

This is the case with bad performance:

------------------------------
 n8763n   (  0)  treetop                                                                              [    0x74b7e11fa2d0] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=- nc=1
 n8762n   (  3)    arraycmp  <arraycmp>[#241  helper Method] [flags 0x400 0x0 ] (in GPR_0163) (arrayCmpLen )  [    0x74b7e11fa280] bci=[0,10,106] rc=3 vc=4277 vn=- li=10 udi=35760 nc=3 flg=
0x8020
 n8749n   (  0)      aladd (in GPR_0160) (X>=0 internalPtr )                                          [    0x74b7e11f9e70] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=34944 nc=2 flg=0x8100
 n33n     (  0)        ==>l2a (in &GPR_0104) (X!=0 )
 n8751n   (  0)        lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )                              [    0x74b7e11f9f10] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=- nc=0 flg=0x5104
 n8755n   (  0)      aladd (in GPR_0161) (X>=0 internalPtr )                                          [    0x74b7e11fa050] bci=[0,13,106] rc=0 vc=4277 vn=- li=10 udi=35216 nc=2 flg=0x8100
 n38n     (  0)        ==>l2a (in &GPR_0105)
 n8751n   (  0)        ==>lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )
 n8761n   (  0)      i2l (in GPR_0162) (highWordZero X>=0 )                                           [    0x74b7e11fa230] bci=[0,4,105] rc=0 vc=4277 vn=- li=10 udi=35488 nc=1 flg=0x4100
 n411n    (  2)        ==>iloadi (in GPR_0112) (X>=0 cannotOverflow )
------------------------------

 [    0x74b6af5d88f0]   10      addi    GPR_0160, &GPR_0104, 8
 [    0x74b6af5d8a00]   13      addi    GPR_0161, &GPR_0105, 8
 [    0x74b6af5d8b10]   4       extsw   GPR_0162, GPR_0112
 [    0x74b6af5d9970]   10      Label L0337:    ; (Start of internal control flow)
 [    0x74b6af5d9a00]   10      li      GPR_0163, 0000000000000000
 [    0x74b6af5d9aa0]   10      cmpdi   CCR_0169, GPR_0162, 4
 [    0x74b6af5d9b40]   10      blt     CCR_0169, Label L0346
 [    0x74b6af5d9be0]   10      cmpdi   CCR_0169, GPR_0162, 8
 [    0x74b6af5d9c80]   10      blt     CCR_0169, Label L0345
 [    0x74b6af5d9d20]   10      cmpdi   CCR_0169, GPR_0162, 16
 [    0x74b6af5d9dc0]   10      blt     CCR_0169, Label L0343
 [    0x74b6af5d9e60]   10      sradi   GPR_0164, GPR_0162, 4
 [    0x74b6af5d9f00]   10      mtctr   GPR_0164
 [    0x74b6af5d9fa0]   10      Label L0338:
 [    0x74b6af5da030]   10      lxvw4x  VRF_0166, GPR_0163, GPR_0160
 [    0x74b6af5da0d0]   10      lxvw4x  VRF_0167, GPR_0163, GPR_0161
 [    0x74b6af5da170]   10      vcmpequb.       VRF_0166, VRF_0166, VRF_0167
 [    0x74b6af5da210]   10      bge     CCR_0169, Label L0339
 [    0x74b6af5da2b0]   10      addi    GPR_0163, GPR_0163, 16
 [    0x74b6af5da350]   10      bdnz    CCR_0169, Label L0338
 [    0x74b6af5da3f0]   10      b       Label L0342
 [    0x74b6af5da480]   10      Label L0339:
 [    0x74b6af5da510]   10      vspltisw        VRF_0167, FFFFFFFFFFFFFFF0
 [    0x74b6af5da5b0]   10      vrlw    VRF_0166, VRF_0166, VRF_0167
 [    0x74b6af5da650]   10      vspltish        VRF_0167, 0000000000000008
 [    0x74b6af5da6f0]   10      vrlh    VRF_0166, VRF_0166, VRF_0167
 [    0x74b6af5da790]   10      vnor    VRF_0166, VRF_0166, VRF_0166
 [    0x74b6af5da830]   10      mfvsrd  GPR_0164, VRF_0166
 [    0x74b6af5da8d0]   10      cntlzd  GPR_0164, GPR_0164
 [    0x74b6af5da970]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x74b6af5daa10]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x74b6af5daab0]   10      cmpdi   CCR_0169, GPR_0164, 8
 [    0x74b6af5dab50]   10      bne     CCR_0169, Label L0350
 [    0x74b6af5dabf0]   10      xxpermdi        VRF_0166, VRF_0166, VRF_0166, 0000000000000003
 [    0x74b6af5daca0]   10      mfvsrd  GPR_0164, VRF_0166
 [    0x74b6af5dad40]   10      cntlzd  GPR_0164, GPR_0164
 [    0x74b6af5dade0]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x74b6af5dae80]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x74b6af5daf20]   10      b       Label L0350
 [    0x74b6af5dafb0]   10      Label L0340:
 [    0x74b6af5db040]   10      addi    GPR_0163, GPR_0163, -4
 [    0x74b6af5db0e0]   10      Label L0341:
 [    0x74b6af5db170]   10      cmpb    GPR_0164, GPR_0164, GPR_0165
 [    0x74b6af5db210]   10      nor     GPR_0164, GPR_0164, GPR_0164
 [    0x74b6af5db2b0]   10      cntlzd  GPR_0164, GPR_0164
 [    0x74b6af5db350]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x74b6af5db3f0]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x74b6af5db490]   10      b       Label L0350
 [    0x74b6af5db520]   10      Label L0342:
 [    0x74b6af5db5b0]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x74b6af5db650]   10      cmpdi   CCR_0169, GPR_0164, 4
 [    0x74b6af5db6f0]   10      blt     CCR_0169, Label L0347
 [    0x74b6af5db790]   10      cmpdi   CCR_0169, GPR_0164, 8
 [    0x74b6af5db830]   10      blt     CCR_0169, Label L0344
 [    0x74b6af5db8d0]   10      Label L0343:
 [    0x74b6af5db960]   10      ldbrx   GPR_0164, GPR_0163, GPR_0160
 [    0x74b6af5dba00]   10      ldbrx   GPR_0165, GPR_0163, GPR_0161
 [    0x74b6af5dbaa0]   10      cmpd    CCR_0169, GPR_0164, GPR_0165
 [    0x74b6af5dbb40]   10      bne     CCR_0169, Label L0341
 [    0x74b6af5dbbe0]   10      addi    GPR_0163, GPR_0163, 8
 [    0x74b6af5dbc80]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x74b6af5dbd20]   10      Label L0344:
 [    0x74b6af5dbdb0]   10      cmpdi   CCR_0169, GPR_0164, 4
 [    0x74b6af5dbe50]   10      blt     CCR_0169, Label L0347
 [    0x74b6af5dbef0]   10      Label L0345:
 [    0x74b6af5dbf80]   10      lwbrx   GPR_0164, GPR_0163, GPR_0160
 [    0x74b6af5dc020]   10      lwbrx   GPR_0165, GPR_0163, GPR_0161
 [    0x74b6af5dc0c0]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x74b6af5dc160]   10      bne     CCR_0169, Label L0340
 [    0x74b6af5dc200]   10      addi    GPR_0163, GPR_0163, 4
 [    0x74b6af5dc2a0]   10      Label L0346:
 [    0x74b6af5dc330]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x74b6af5dc3d0]   10      Label L0347:
 [    0x74b6af5dc460]   10      beq     CCR_0168, Label L0349
 [    0x74b6af5dc500]   10      mtctr   GPR_0164
 [    0x74b6af5dc5a0]   10      Label L0348:
 [    0x74b6af5dc630]   10      lbzx    GPR_0164, GPR_0163, GPR_0160
 [    0x74b6af5dc6d0]   10      lbzx    GPR_0165, GPR_0163, GPR_0161
 [    0x74b6af5dc770]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x74b6af5dc810]   10      bne     CCR_0169, Label L0351
 [    0x74b6af5dc8b0]   10      addi    GPR_0163, GPR_0163, 1
 [    0x74b6af5dc950]   10      bdnz    CCR_0169, Label L0348
 [    0x74b6af5dc9f0]   10      Label L0349:
 [    0x74b6af5dca80]   10      Label L0350:
 [    0x74b6af5dcb10]   10      Label L0351:
 [    0x74b6af5dcba0]   10      Label L0352:    ; (End of internal control flow)

And this is the code diff between the two runs:

diff --git a/compiler/p/codegen/OMRTreeEvaluator.cpp b/compiler/p/codegen/OMRTreeEvaluator.cpp
index 912a5f4..9a54876 100644
--- a/compiler/p/codegen/OMRTreeEvaluator.cpp
+++ b/compiler/p/codegen/OMRTreeEvaluator.cpp
@@ -5676,8 +5676,9 @@ static TR::Register *inlineVectorArrayCmp(TR::Node *node, TR::CodeGenerator *cg)
    generateTrg1ImmInstruction(cg, TR::InstOpCode::vspltish, node, vec2Reg, 8);
    generateTrg1Src2Instruction(cg, TR::InstOpCode::vrlh, node, vec1Reg, vec1Reg, vec2Reg);

+   generateTrg1Src2Instruction(cg, TR::InstOpCode::vnor, node, vec1Reg, vec1Reg, vec1Reg);
    generateTrg1Src1Instruction(cg, TR::InstOpCode::mfvsrd, node, tempReg, vec1Reg);
-   generateTrg1Src2Instruction(cg, TR::InstOpCode::nor, node, tempReg, tempReg, tempReg);
+   //generateTrg1Src2Instruction(cg, TR::InstOpCode::nor, node, tempReg, tempReg, tempReg);
    generateTrg1Src1Instruction(cg, TR::InstOpCode::cntlzd, node, tempReg, tempReg);
    generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::sradi, node, tempReg, tempReg, 3);
    generateTrg1Src2Instruction(cg, TR::InstOpCode::add, node, offsetReg, offsetReg, tempReg);
@@ -5686,7 +5687,7 @@ static TR::Register *inlineVectorArrayCmp(TR::Node *node, TR::CodeGenerator *cg)

    generateTrg1Src2ImmInstruction(cg, TR::InstOpCode::xxpermdi, node, vec1Reg, vec1Reg, vec1Reg, 3);
    generateTrg1Src1Instruction(cg, TR::InstOpCode::mfvsrd, node, tempReg, vec1Reg);
-   generateTrg1Src2Instruction(cg, TR::InstOpCode::nor, node, tempReg, tempReg, tempReg);
+   //generateTrg1Src2Instruction(cg, TR::InstOpCode::nor, node, tempReg, tempReg, tempReg);
    generateTrg1Src1Instruction(cg, TR::InstOpCode::cntlzd, node, tempReg, tempReg);
    generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::sradi, node, tempReg, tempReg, 3);
    generateTrg1Src2Instruction(cg, TR::InstOpCode::add, node, offsetReg, offsetReg, tempReg);

The code changes were to the path that handles a mismatched byte after the vector loads. Since the two arrays are identical this path was not expected to be taken. To be certain, I step through the assembly in gdb and confirmed that this path was not taken.

@zl-wang Do you have any idea on why this might have happened?

@zl-wang

zl-wang commented Jun 16, 2023

Copy link
Copy Markdown
Contributor

only 32byte-equal case is slower? no other cases became slower?

because different code sequence isn't even run in this case, it is indeed hard to understand the slow-down. i can speculate: one instruction less makes branch-prediction hashed to the same entry such that mis-prediction happens more. one way to verify this speculation is to insert a nop in the vnor case, making up that one-less situation. and test again.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I actually just tried adding an extra nop. That makes the performance problem go away for the 32 byte array case.

This was the specific case I was looking at but there were also other unusual cases like:
19 byte array with a mismatch at index 17 getting 13% worse
26 byte matching array getting 12% worse

@zl-wang

zl-wang commented Jun 16, 2023

Copy link
Copy Markdown
Contributor

because you are comparing the same logical sequences with different path-lengths with slower cases of one instruction less (everything else is the same), the likely reasons for slowing down: branch prediction related and instruction issue related. your nop test just pinned it to be branch prediction related.

I think it probably isn't worth of narrowing down which branch(es) are involved (by inserting the nop at different locations). 19byte case: one vector iteration and mismatching in the first residue iteration etc. i guessed 17byte mismatching on the last byte should be similar. did you see any vnor faster case(s)? especially with the nop inserted.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

Changing nor to vnor shows the most improvements in these cases:
18 byte matching array is 34% faster
3 byte matching array is 30% faster
10 byte matching array is 26% faster

Since these are all matching arrays they should all be running the exact same instructions but are faster anyways.

I haven't tried re-running everything with the nop. But, I did retry the 19 byte array with mismatch an index 17 and it was faster (about the same as going back to nor instead of vnor).

@zl-wang

zl-wang commented Jun 16, 2023

Copy link
Copy Markdown
Contributor

you haven't tried vclzd to replace the two cntlzd and no-loop residue. with vclzd (one instruction less further), the nop might not be needed. run a few times each to see the stable/reliable timing. once we landed on the sweet spot, we can stick to it.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I just tried using vclzd instead of two cntlzd instructions and I run into the same reduced performance for matching 32 byte arrays. I need to pad it with 2 nops to get a good performance again.

@zl-wang

zl-wang commented Jun 16, 2023

Copy link
Copy Markdown
Contributor

can you show where you inserted 2 nops to recover the performance?

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The latest version looks like this:

------------------------------
 n8763n   (  0)  treetop                                                                              [    0x7f58bbc5a2d0] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=- nc=1
 n8762n   (  3)    arraycmp  <arraycmp>[#241  helper Method] [flags 0x400 0x0 ] (in GPR_0163) (arrayCmpLen )  [    0x7f58bbc5a280] bci=[0,10,106] rc=3 vc=4277 vn=- li=10 udi=35760 nc=3 flg=0x8020
 n8749n   (  0)      aladd (in GPR_0160) (X>=0 internalPtr )                                          [    0x7f58bbc59e70] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=34944 nc=2 flg=0x8100
 n33n     (  0)        ==>l2a (in &GPR_0104) (X!=0 )
 n8751n   (  0)        lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )                              [    0x7f58bbc59f10] bci=[0,10,106] rc=0 vc=4277 vn=- li=10 udi=- nc=0 flg=0x5104
 n8755n   (  0)      aladd (in GPR_0161) (X>=0 internalPtr )                                          [    0x7f58bbc5a050] bci=[0,13,106] rc=0 vc=4277 vn=- li=10 udi=35216 nc=2 flg=0x8100
 n38n     (  0)        ==>l2a (in &GPR_0105)
 n8751n   (  0)        ==>lconst 8 (highWordZero X!=0 X>=0 cannotOverflow )
 n8761n   (  0)      i2l (in GPR_0162) (highWordZero X>=0 )                                           [    0x7f58bbc5a230] bci=[0,4,105] rc=0 vc=4277 vn=- li=10 udi=35488 nc=1 flg=0x4100
 n411n    (  2)        ==>iloadi (in GPR_0112) (X>=0 cannotOverflow )
------------------------------

 [    0x7f578a5d88f0]   10      addi    GPR_0160, &GPR_0104, 8
 [    0x7f578a5d8a00]   13      addi    GPR_0161, &GPR_0105, 8
 [    0x7f578a5d8b10]   4       extsw   GPR_0162, GPR_0112
 [    0x7f578a5d98b0]   10      Label L0337:    ; (Start of internal control flow)
 [    0x7f578a5d9940]   10      li      GPR_0163, 0000000000000000
 [    0x7f578a5d99e0]   10      cmpdi   CCR_0169, GPR_0162, 4
 [    0x7f578a5d9a80]   10      blt     CCR_0169, Label L0345
 [    0x7f578a5d9b20]   10      cmpdi   CCR_0169, GPR_0162, 8
 [    0x7f578a5d9bc0]   10      blt     CCR_0169, Label L0344
 [    0x7f578a5d9c60]   10      cmpdi   CCR_0169, GPR_0162, 16
 [    0x7f578a5d9d00]   10      blt     CCR_0169, Label L0343
 [    0x7f578a5d9da0]   10      sradi   GPR_0164, GPR_0162, 4
 [    0x7f578a5d9e40]   10      mtctr   GPR_0164
 [    0x7f578a5d9ee0]   10      Label L0338:
 [    0x7f578a5d9f70]   10      lxvw4x  VRF_0166, GPR_0163, GPR_0160
 [    0x7f578a5da010]   10      lxvw4x  VRF_0167, GPR_0163, GPR_0161
 [    0x7f578a5da0b0]   10      vcmpequb.       VRF_0166, VRF_0166, VRF_0167
 [    0x7f578a5da150]   10      bge     CCR_0169, Label L0339
 [    0x7f578a5da1f0]   10      addi    GPR_0163, GPR_0163, 16
 [    0x7f578a5da290]   10      bdnz    CCR_0169, Label L0338
 [    0x7f578a5da330]   10      b       Label L0342
 [    0x7f578a5da3c0]   10      Label L0339:
 [    0x7f578a5da450]   10      vspltisw        VRF_0167, FFFFFFFFFFFFFFF0
 [    0x7f578a5da4f0]   10      vrlw    VRF_0166, VRF_0166, VRF_0167
 [    0x7f578a5da590]   10      vspltish        VRF_0167, 0000000000000008
 [    0x7f578a5da630]   10      vrlh    VRF_0166, VRF_0166, VRF_0167
 [    0x7f578a5da6d0]   10      vnor    VRF_0166, VRF_0166, VRF_0166
 [    0x7f578a5da770]   10      vclzd   VRF_0166, VRF_0166
 [    0x7f578a5da810]   10      mfvsrd  GPR_0164, VRF_0166
 [    0x7f578a5da8b0]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x7f578a5da950]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x7f578a5da9f0]   10      cmpdi   CCR_0169, GPR_0164, 8
 [    0x7f578a5daa90]   10      bne     CCR_0169, Label L0348
 [    0x7f578a5dab30]   10      xxpermdi        VRF_0166, VRF_0166, VRF_0166, 0000000000000003
 [    0x7f578a5dabe0]   10      mfvsrd  GPR_0164, VRF_0166
 [    0x7f578a5dac80]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x7f578a5dad20]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x7f578a5dadc0]   10      b       Label L0348
 [    0x7f578a5dae50]   10      ori     GPR_0164, GPR_0164, 0
 [    0x7f578a5daef0]   10      ori     GPR_0164, GPR_0164, 0
 [    0x7f578a5daf90]   10      Label L0340:
 [    0x7f578a5db020]   10      addi    GPR_0163, GPR_0163, -4
 [    0x7f578a5db0c0]   10      Label L0341:
 [    0x7f578a5db150]   10      cmpb    GPR_0164, GPR_0164, GPR_0165
 [    0x7f578a5db1f0]   10      nor     GPR_0164, GPR_0164, GPR_0164
 [    0x7f578a5db290]   10      cntlzd  GPR_0164, GPR_0164
 [    0x7f578a5db330]   10      sradi   GPR_0164, GPR_0164, 3
 [    0x7f578a5db3d0]   10      add     GPR_0163, GPR_0163, GPR_0164
 [    0x7f578a5db470]   10      b       Label L0348
 [    0x7f578a5db500]   10      Label L0342:
 [    0x7f578a5db590]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x7f578a5db630]   10      cmpdi   CCR_0169, GPR_0164, 4
 [    0x7f578a5db6d0]   10      blt     CCR_0169, Label L0346
 [    0x7f578a5db770]   10      cmpdi   CCR_0169, GPR_0164, 8
 [    0x7f578a5db810]   10      blt     CCR_0169, Label L0344
 [    0x7f578a5db8b0]   10      Label L0343:
 [    0x7f578a5db940]   10      ldbrx   GPR_0164, GPR_0163, GPR_0160
 [    0x7f578a5db9e0]   10      ldbrx   GPR_0165, GPR_0163, GPR_0161
 [    0x7f578a5dba80]   10      cmpd    CCR_0169, GPR_0164, GPR_0165
 [    0x7f578a5dbb20]   10      bne     CCR_0169, Label L0341
 [    0x7f578a5dbbc0]   10      addi    GPR_0163, GPR_0163, 8
 [    0x7f578a5dbc60]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x7f578a5dbd00]   10      cmpdi   CCR_0169, GPR_0164, 4
 [    0x7f578a5dbda0]   10      blt     CCR_0169, Label L0346
 [    0x7f578a5dbe40]   10      Label L0344:
 [    0x7f578a5dbed0]   10      lwbrx   GPR_0164, GPR_0163, GPR_0160
 [    0x7f578a5dbf70]   10      lwbrx   GPR_0165, GPR_0163, GPR_0161
 [    0x7f578a5dc010]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x7f578a5dc0b0]   10      bne     CCR_0169, Label L0340
 [    0x7f578a5dc150]   10      addi    GPR_0163, GPR_0163, 4
 [    0x7f578a5dc1f0]   10      Label L0345:
 [    0x7f578a5dc280]   10      subf.   GPR_0164, GPR_0163, GPR_0162
 [    0x7f578a5dc320]   10      Label L0346:
 [    0x7f578a5dc3b0]   10      beq     CCR_0168, Label L0347
 [    0x7f578a5dc450]   10      lbzx    GPR_0164, GPR_0163, GPR_0160
 [    0x7f578a5dc4f0]   10      lbzx    GPR_0165, GPR_0163, GPR_0161
 [    0x7f578a5dc590]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x7f578a5dc630]   10      bne     CCR_0169, Label L0349
 [    0x7f578a5dc6d0]   10      addi    GPR_0163, GPR_0163, 1
 [    0x7f578a5dc770]   10      cmpd    CCR_0169, GPR_0163, GPR_0162
 [    0x7f578a5dc810]   10      beq     CCR_0169, Label L0347
 [    0x7f578a5dc8b0]   10      lbzx    GPR_0164, GPR_0163, GPR_0160
 [    0x7f578a5dc950]   10      lbzx    GPR_0165, GPR_0163, GPR_0161
 [    0x7f578a5dc9f0]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x7f578a5dca90]   10      bne     CCR_0169, Label L0349
 [    0x7f578a5dcb30]   10      addi    GPR_0163, GPR_0163, 1
 [    0x7f578a5dcbd0]   10      cmpd    CCR_0169, GPR_0163, GPR_0162
 [    0x7f578a5dcc70]   10      beq     CCR_0169, Label L0347
 [    0x7f578a5dcd10]   10      lbzx    GPR_0164, GPR_0163, GPR_0160
 [    0x7f578a5dcdb0]   10      lbzx    GPR_0165, GPR_0163, GPR_0161
 [    0x7f578a5dce50]   10      cmpw    CCR_0169, GPR_0164, GPR_0165
 [    0x7f578a5dcef0]   10      bne     CCR_0169, Label L0349
 [    0x7f578a5dcf90]   10      addi    GPR_0163, GPR_0163, 1
 [    0x7f578a5dd030]   10      Label L0347:
 [    0x7f578a5dd0c0]   10      Label L0348:
 [    0x7f578a5dd150]   10      Label L0349:
 [    0x7f578a5dd1e0]   10      Label L0350:    ; (End of internal control flow)

The two nop are:

 [    0x7f578a5dadc0]   10      b       Label L0348
 [    0x7f578a5dae50]   10      ori     GPR_0164, GPR_0164, 0
 [    0x7f578a5daef0]   10      ori     GPR_0164, GPR_0164, 0
 [    0x7f578a5daf90]   10      Label L0340:

They are in unreachable code so they never run themselves.

@zl-wang

zl-wang commented Jun 19, 2023

Copy link
Copy Markdown
Contributor

never-executed nop(s) make the performance better for some cases (some of which have no actual loop). that is a clear indicator branch-prediction aliasing is likely the problem. at the same time, it is unclear whether vclzd (and two nops) or two cntlzd (and one nop) is better, such that i have no preference in this choice. either is fine with me. let's wrap it up with further testing of no residue loop (likely no benefit ... in my mind).

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The new changes are as follows:
-In the vector mismatch section, vnor is used instead of two nor instructions
-skipLoad8Label2 was removed. This was previously done to do a check to see if there were at least 4 bytes left to process. But, this check is already done and it it known there are at least 4 bytes left. As such, the jump is now to skipLoad8Label which is right before the 4 byte loads
-byte by byte residue loop was unrolled. It can happen a maximum of 3 times and there is a check after each to confirm there is enough data left to load
-In the vector mismatch section, vclzd is used instead of two cntlzd instructions
-To work around the branch prediction issues in the test, two nop were added in a place that they are neven run to adjust the alignment.

This generates the assembly shown in the previous comment:
#7011 (comment)

The new performance data looks like this:

Length Baseline (s) Vector (s) Diff%
1 3.643 3.244 12.3%
2 4.383 3.934 11.4%
3 5.761 4.501 28%
4 5.947 3.125 90.3%
5 6.769 3.893 73.9%
6 7.734 4.557 69.7%
7 8.449 5.201 62.4%
8 4.831 3.935 22.8%
9 6.291 4.689 34.2%
10 7.322 5.244 39.6%
11 7.965 6.118 30.2%
12 8.98 4.495 99.8%
13 9.846 5.249 87.6%
14 11.047 5.781 91.1%
15 12.034 6.51 84.9%
16 5.622 5.234 7.4%
17 7.474 6.109 22.3%
18 8.058 6.346 27%
19 8.834 7.112 24.2%
20 9.53 5.885 61.9%
21 11.041 6.661 65.8%
22 11.722 7.42 58%
23 12.797 7.933 61.3%
24 6.468 6.244 3.6%
25 8.107 6.89 17.7%
26 8.925 7.601 17.4%
27 9.586 7.996 19.9%
28 10.422 6.787 53.6%
29 12.42 7.374 68.4%
30 12.667 7.944 59.5%
31 13.615 8.706 56.4%
32 7.289 7.464 -2.3%
39 14.334 9.128 57%
40 8.27 7.702 7.4%
47 15.248 9.785 55.8%
48 9.253 8.923 3.7%
55 16.064 10.362 55%
56 9.967 8.938 11.5%
63 16.894 10.843 55.8%
64 10.577 9.541 10.9%
71 17.621 11.482 53.5%
72 11.479 9.822 16.9%
79 18.419 12.096 52.3%
80 12.261 10.977 11.7%
87 19.238 12.568 53.1%
88 13.195 11.237 17.4%
95 20.23 13.516 49.7%
96 17.382 11.269 54.2%
103 23.45 13.09 79.1%
104 19.157 11.951 60.3%
111 24.691 13.989 76.5%
112 20.125 12.789 57.4%
119 25.207 14.571 73%
120 19.996 13.475 48.4%
127 25.996 15.23 70.7%
128 20.759 14.501 43.2%
1000 114.641 82.202 39.5%

The worst case is still the 32 byte matching array case. But, I think this is just a testing anomoly since this path uses vector loads twice and doesn't have a mismatch in the loaded values. This case should be better than the old system which uses 8 byte loads 4 times instead.

Length Mismatch Idx Baseline (s) Vector (s) Diff%
3 0 4.605 4.456 3.3%
3 1 6.151 4.516 36.2%
3 2 6.1 4.713 29.4%
7 0 4.479 5.393 -16.9%
7 1 6.333 5.398 17.3%
7 2 6.218 5.299 17.3%
7 3 6.962 5.397 29%
7 4 7.848 4.573 71.6%
7 5 8.895 4.766 86.6%
7 6 9.709 5.411 79.4%
11 0 6.159 5.442 13.2%
11 1 7.12 5.532 28.7%
11 2 7.944 5.535 43.5%
11 3 9.02 5.442 65.7%
11 4 9.869 5.526 78.6%
11 5 10.908 5.53 97.3%
11 6 12.027 5.532 117.4%
11 7 12.913 5.539 133.1%
11 8 6.314 5.338 18.3%
11 9 7.394 5.811 27.2%
11 10 7.91 6.186 27.9%
15 0 6.279 5.434 15.6%
15 1 7.238 5.437 33.1%
15 2 8.079 5.529 46.1%
15 3 8.927 5.533 61.3%
15 4 9.971 5.439 83.3%
15 5 10.989 5.53 98.7%
15 6 12.095 5.531 118.7%
15 7 13.032 5.422 140.4%
15 8 6.322 5.769 9.6%
15 9 7.277 5.759 26.4%
15 10 7.913 5.77 37.1%
15 11 8.677 5.637 53.9%
15 12 11.153 5.257 112.2%
15 13 11.063 5.864 88.7%
15 14 12.31 6.791 81.3%
19 0 6.156 6.115 0.7%
19 1 7.238 6.119 18.3%
19 2 7.958 6.119 30.1%
19 3 9.018 6.124 47.3%
19 4 9.971 6.55 52.2%
19 5 10.991 6.116 79.7%
19 6 11.97 6.023 98.7%
19 7 13.029 5.994 117.4%
19 8 7.904 6.401 23.5%
19 9 8.079 6.404 26.2%
19 10 8.874 6.532 35.9%
19 11 9.878 6.54 51%
19 12 10.79 6.539 65%
19 13 11.702 6.404 82.7%
19 14 12.726 6.549 94.3%
19 15 14.254 6.539 118%
19 16 7.293 6.373 14.4%
19 17 7.978 6.644 20.1%
19 18 8.794 7.183 22.4%
23 0 6.279 6.027 4.2%
23 1 7.241 5.989 20.9%
23 2 7.955 6.002 32.5%
23 3 8.924 6.132 45.5%
23 4 9.964 5.993 66.3%
23 5 10.909 6.117 78.3%
23 6 12.091 6.523 85.4%
23 7 12.895 6.122 110.6%
23 8 7.779 6.53 19.1%
23 9 8.08 6.399 26.3%
23 10 8.844 6.543 35.2%
23 11 9.876 6.394 54.5%
23 12 10.795 6.906 56.3%
23 13 11.834 6.4 84.9%
23 14 12.739 6.55 94.5%
23 15 14.157 6.405 121%
23 16 7.422 7.172 3.5%
23 17 7.975 7.162 11.4%
23 18 8.665 7.284 19%
23 19 9.516 7.174 32.6%
23 20 10.72 6.583 62.8%
23 21 11.815 7.677 53.9%
23 22 13.808 7.983 73%
27 0 6.186 5.706 8.4%
27 1 7.141 5.708 25.1%
27 2 7.955 5.706 39.4%
27 3 8.94 5.705 56.7%
27 4 9.996 5.693 75.6%
27 5 10.935 5.701 91.8%
27 6 12.114 5.818 108.2%
27 7 12.922 5.709 126.3%
27 8 7.843 6.105 28.5%
27 9 8.094 6.118 32.3%
27 10 8.856 6.108 45%
27 11 9.718 6.098 59.4%
27 12 10.675 6.262 70.5%
27 13 11.747 6.263 87.6%
27 14 12.884 6.1 111.2%
27 15 14.16 6.264 126.1%
27 16 7.997 6.773 18.1%
27 17 9.823 6.777 44.9%
27 18 9.68 6.784 42.7%
27 19 10.585 6.643 59.3%
27 20 11.535 6.643 73.6%
27 21 12.77 6.775 88.5%
27 22 14.022 6.621 111.8%
27 23 15.745 6.796 131.7%
27 24 8.003 6.789 17.9%
27 25 8.876 7.65 16%
27 26 9.505 8.237 15.4%
31 0 6.309 5.709 10.5%
31 1 7.144 5.713 25%
31 2 7.964 5.706 39.6%
31 3 8.924 5.719 56%
31 4 9.994 5.706 75.1%
31 5 10.922 5.705 91.4%
31 6 11.983 5.844 105%
31 7 13.038 5.831 123.6%
31 8 7.966 6.116 30.2%
31 9 8.092 6.693 20.9%
31 10 8.98 6.107 47%
31 11 9.716 6.121 58.7%
31 12 10.685 6.255 70.8%
31 13 11.854 6.111 94%
31 14 12.884 6.123 110.4%
31 15 14.284 6.264 128%
31 16 8.141 6.782 20%
31 17 9.703 6.647 46%
31 18 9.866 6.642 48.5%
31 19 10.564 6.645 59%
31 20 11.563 6.781 70.5%
31 21 12.658 6.63 90.9%
31 22 14.133 6.778 108.5%
31 23 15.648 6.773 131%
31 24 8.026 7.383 8.7%
31 25 8.861 7.544 17.5%
31 26 9.527 7.556 26.1%
31 27 10.59 7.408 43%
31 28 11.534 7.335 57.2%
31 29 12.803 7.829 63.5%
31 30 13.86 8.88 56.1%

The one bad case here is the 7 length array with a mismatch on the first byte which is a real degredation since the new version is worse at this case than the old version. Note that the baseline value for this case happened to be better than the last time I took the baseline. My latest changes didn't do anything that would effect this particular case so result should be about the same as they were last time.

On top of these change, I also made a new change to reduce the number of jumps taken in some cases. I will be writing an update about this shortly.

@IBMJimmyk
IBMJimmyk requested a review from 0xdaryl as a code owner November 7, 2023 21:33
@IBMJimmyk IBMJimmyk changed the title WIP: Vector implementation for arraycmp Vector implementation for arraycmp on P8/P9 Nov 8, 2023
@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I made a change to the code to explicitly check for the datatype of the length child since it can be either Int32 or Int64 and also added a bunch of comments to the code. I retested the code and everything seems to work.

@zl-wang Could you help review the code and see if anything else needs to be changed?

Comment thread compiler/p/codegen/OMRTreeEvaluator.cpp Outdated
"lengthNode not int64 or int32. Datatype: %s", node->getDataType().toString());

if (isArrayCmpLen && !is64bit)
if (!is64bit && isLengthNode64bit)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

from Spencer comment, it seems impossible to have 64bit length node on 32bit platforms. we have no need to handle this combination. plus, it is functionally useless to have that combination. furthermore, it adds complexity to handle register-pair for long on 32bit platforms.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

It is possible to have a 64bit length node on 32bit. I am able to get trees like this on 32bit AIX:

n524n     iRegStore gr2                                                                       [0xef0972c0] bci=[-1,14,4] rc=0 vc=1494 vn=- li=4 udi=- nc=1
n521n       l2i                                                                               [0xef097200] bci=[-1,10,4] rc=3 vc=1494 vn=- li=4 udi=- nc=1
n520n         arraycmplen  <arraycmplen>[#244  helper Method] [flags 0x400 0x0 ] ()           [0xef0971c0] bci=[-1,10,4] rc=1 vc=1494 vn=- li=4 udi=- nc=3 flg=0x20
n509n           aiadd (X>=0 internalPtr )                                                     [0xef096f00] bci=[-1,10,4] rc=1 vc=1494 vn=- li=4 udi=- nc=2 flg=0x8100
n546n             ==>aRegLoad
n511n             iconst 8 (X!=0 X>=0 )                                                       [0xef096f80] bci=[-1,10,4] rc=2 vc=1494 vn=- li=4 udi=- nc=0 flg=0x104
n514n           aiadd (X>=0 internalPtr )                                                     [0xef097040] bci=[-1,13,4] rc=1 vc=1494 vn=- li=4 udi=- nc=2 flg=0x8100
n545n             ==>aRegLoad
n511n             ==>iconst 8
n519n           iu2l (highWordZero X>=0 )                                                     [0xef097180] bci=[-1,4,3] rc=1 vc=1494 vn=- li=4 udi=- nc=1 flg=0x4100
n13n              ==>iloadi

The length node is a 64bit value in this case.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

but IdiomTransformations will insert an i2l on the length child on 64-bit platforms. (quoted from above)

please confirm it with @Spencer-Comin.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

arraycmplen will always have a length node that is TR::Int64, but it should be impossible for the value to exceed 32 bits on a 32 bit platform (since you can't have an array that occupies 232+ bytes of memory). arraycmp will always have a length node that is TR::Int32 on 32 bit platforms, but on 64 bit platforms it is inconsistent and may be TR::Int32 or TR::Int64.

Comment thread compiler/p/codegen/OMRTreeEvaluator.cpp Outdated
bool is64bit = cg->comp()->target().is64Bit();
bool isLengthNode64bit = lengthNode->getDataType().isInt64();
TR_ASSERT_FATAL_WITH_NODE(lengthNode, lengthNode->getDataType().isInt64() || lengthNode->getDataType().isInt32(),
"lengthNode not int64 or int32. Datatype: %s", node->getDataType().toString());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

provides more precise assert ... need to cover the case: no 64bit length node on 32bit platforms.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

This should be good to go now. The length node is now always 64 bit. This is for both arraycmp and arraycmplen on both 64bit and 32bit.

@zl-wang Can you take another look when you get a chance?

Comment thread compiler/p/codegen/OMRTreeEvaluator.cpp Outdated
*/
if (is64bit)
{
generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::sradi, node, tempReg, lengthReg, 4);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't logical-shift be used here?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I can change it, but it shouldn't matter either way. Length values where the highest bit is set shouldn't happen.


/* If a difference is found in the 16 loaded bytes, jump to load16DiffLabel to figure out where the first mismatched byte is. */
generateTrg1Src2Instruction(cg, TR::InstOpCode::vcmpequb_r, node, vec1Reg, vec1Reg, vec2Reg);
generateConditionalBranchInstruction(cg, TR::InstOpCode::bge, node, load16DiffLabel, cr6Reg);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

the resulting condition cannot be "greater than" i.e. positive. the more appropriate bc instruction here is beq, i.e. false compare result is set in "equal" bit, although i understood bge here is equivalent to beq in effect.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

vcmpequb. changes the condition register is a special way. If all the bytes in the two vector registers match, the all_true condition is met. This causes the negative bit to be set in CR6. The following instruction wants to jump to load16DiffLabel if a difference in the bytes is found. This is done by testing "greater than or equal" on CR6 since if a difference is found, the negative bit will be clear.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

exactly: CR6 = t || 0b0 || f || 0b0 t & f are true/false from the comparison as you described above. i.e. only lt and eq bits are possibly set. should not be bothered to test gt bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

bge only checks 1 bit. It checks if the negative bit is clear. For example:

0x315efe90 00000098 [0xef2f0680] 10000c06          10   vcmpequb.       vr0, vr0, vr1
0x315efe94 0000009c [0xef2f06d0] 409800e8          10   bge     cr6, Label L0169

0x409800e8 -> bc 4, 24, targetAddr
The first 4 means check if the condition bit is 0.
24 means check (24+32=) bit 56. Bit 56 is the negative flag of CR6.
So it will branch if the negative flag on CR6 is 0 which is what I want.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ok. that sounds about right.

Comment thread compiler/p/codegen/OMRTreeEvaluator.cpp Outdated
generateConditionalBranchInstruction(cg, TR::InstOpCode::beq, node, returnTrueLabel, cr0Reg);

/* If there are less than 4 bytes left, jump to the byte by byte handling. */
generateTrg1Src1ImmInstruction(cg, is64bit ? TR::InstOpCode::cmpi8 : TR::InstOpCode::cmpli4, node, cr6Reg, tempReg, 4);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

is there any benefit in using diff cmpi instruction here, given the expected tempReg value? it is not that cmpli4 doesn't work in 64bit mode.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

There is no difference. tempReg is never negative at that point or is it ever a large enough positive number for the highest bit to be set. I can change it to be cmpli8 so it looks the same as the 32 bit case.

Comment thread compiler/p/codegen/OMRTreeEvaluator.cpp Outdated
if (is64bit)
{
/* There are at least 4 bytes left. If there are less than 8 bytes left jump to the 4 byte load section. */
generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::cmpi8, node, cr6Reg, tempReg, 8);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

shouldn't cmpli4 be used here? although we don't support 32bit processors anymore.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think cmpli4 can be used there. tempReg technically holds a 64 bit value but it will always be between 4 to 15 at that point.

/*
* Under 64bit, there are at least 8 bytes left at this point but no more than 15.
* Under 32bit, there are at least 4 bytes left at this point but no more than 15.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this comment is misleading ... and you made the handling more complicated than necessary. you can come here only: 1) the length is less than 16; or, 2) you are about to handling the residue; either case is already tested against 4 and 8. no diff to me between 32bit and 64bit.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Under the 32 bit case, the check against 8 is skipped since the section with 8 byte loads is not run under 32 bit. So under 32 bit there are 4-15 bytes left at this point while under 64 bit there are 8-15

generateConditionalBranchInstruction(cg, TR::InstOpCode::blt, node, skipLoad4Label, cr6Reg);

/* There are at least 4 bytes left at this point but no more than 7. */
generateLabelInstruction(cg, TR::InstOpCode::label, node, skipLoad8Label);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

isn't it a problem that this label is only created conditionally?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

The instructions that jump to it are generated under the same condition. As in, the label is only generated under 64 bit but the jumps to the label are also only generated under 64 bit.

generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::addi2, node, offsetReg, offsetReg, 8);

/* If there are no more bytes, the arrays match each other. */
generateTrg1Src2Instruction(cg, TR::InstOpCode::subf_r, node, tempReg, offsetReg, lengthReg);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

it seemed sub-optimal to repeat this subf_r instruction over and over again, but i haven't thought through the alternative yet.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

subf. is used to compare how many bytes have been processed to check if we are done and also determine how much is left so we know what section to run next. I can't think of a better way to do this but tell me if you think of something.

generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::cmpli4, node, cr6Reg, tempReg, 4);
generateConditionalBranchInstruction(cg, TR::InstOpCode::blt, node, skipLoad4Label, cr6Reg);

/* Use 4 byte loads to search for a mismatch between the two arrays. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

this lost me. the coming-in condition is more than 4byte but less than 8bytes. you did a 4-byte previously, so that you have less-than-4 to go, don't you?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is under the 32 bit case. The 32 bit case doesn't do the 8 byte load section. So in this area is has to do the 4 byte load and comparison up to 3 times. Each times, it checks if there are at least 4 bytes left to process.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I updated the cmp instructions used, changed the indicated shift to a logical shift and hopefully made some of the comment more clear. I think this should address all previous comments.

generateConditionalBranchInstruction(cg, TR::InstOpCode::blt, node, skipLoad8Label, cr6Reg);
}

/*

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

here, you have an issue for choice of laying down optimal sequence and catching the most likely cases fast. for example, length more than 16 is most the likely. doing a single 16 comparison up front, then handling the residual looks better, instead of paying the cost of 2 taken branches (6 cycles in total usually on P, assuming every branch is perfectly predicted). progressively testing for longer lengths might end up paying the highest cost for most situations. have you thought through it?

generateTrg1Src1ImmInstruction(cg, TR::InstOpCode::addi2, node, offsetReg, offsetReg, 16);
generateConditionalBranchInstruction(cg, TR::InstOpCode::bdnz, node, load16LoopLabel, cr6Reg);

/* If there are no more bytes, the arrays match each other. */

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

reading the code more, i see no reason not to get rid of the up front 4/8 tests.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

The code is laid out to try and reduce the number of taken branches. This table shows the number of taken branches for each input size:

Input Length Taken branches
1 2
2 2
3 2
4 2
5 2
6 2
7 2
8 2
9 3
10 3
11 3
12 2
13 2
14 2
15 2
16 1
17 2
18 2
19 2
20 2
21 2
22 2
23 2
24 1
25 2
26 2
27 2
28 1
29 1
30 1
31 1

I think a spent a fair amount of time on this before but that was a few months ago so I'll try giving it another look over.

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

Current is the current version with the low size checks first.
New is a potential change with the low size checks performed later.

Length Current (s) New (s) Diff %
1 3.156 3.155 0.0%
2 4.359 4.602 5.6%
3 4.807 5.061 5.3%
4 3.73 4.274 14.6%
5 4.352 4.88 12.1%
6 4.945 5.424 9.7%
7 5.387 6.171 14.6%
8 3.994 4.26 6.7%
9 4.924 5.142 4.4%
10 5.444 5.759 5.8%
11 6.187 6.675 7.9%
12 4.908 5.138 4.7%
13 5.447 5.762 5.8%
14 6.2 6.633 7.0%
15 7.112 7.156 0.6%
16 4.722 4.187 -11.3%
17 5.559 5.029 -9.5%
18 6.06 5.517 -9.0%
19 6.872 6.008 -12.6%
20 5.692 5.237 -8.0%
21 6.385 5.742 -10.1%
22 7.244 6.444 -11.0%
23 7.71 7.255 -5.9%
24 5.689 5.237 -7.9%
25 6.908 6.083 -11.9%
26 7.434 6.939 -6.7%
27 8.087 7.423 -8.2%
28 6.934 6.066 -12.5%
29 7.397 6.938 -6.2%
30 8.111 7.466 -8.0%
31 8.513 8.09 -5.0%
32 5.426 4.953 -8.7%
39 8.418 7.931 -5.8%
40 6.669 6.056 -9.2%
47 9.521 9.098 -4.4%
48 6.851 6.12 -10.7%
55 9.536 9.195 -3.6%
56 7.833 7.315 -6.6%
63 10.209 9.858 -3.4%
64 7.413 6.87 -7.3%
71 10.236 9.92 -3.1%
72 8.45 8.154 -3.5%
79 11.09 10.71 -3.4%
80 8.281 7.828 -5.5%
87 11.043 10.699 -3.1%
88 9.199 8.867 -3.6%
95 11.863 11.552 -2.6%
96 8.955 8.639 -3.5%
103 11.949 11.56 -3.3%
104 10.118 9.794 -3.2%
111 12.743 12.36 -3.0%
112 9.937 9.45 -4.9%
119 12.699 12.373 -2.6%
120 11.018 10.652 -3.3%
127 13.625 13.288 -2.5%
128 10.788 10.352 -4.0%
1000 68.114 68.356 0.4%
Length Mismatch Idx Current (s) New (s) Diff %
3 0 3.594 3.816 6.2%
3 1 4.149 4.478 7.9%
3 2 4.81 5.045 4.9%
7 0 3.945 4.541 15.1%
7 1 3.946 4.532 14.9%
7 2 3.947 4.531 14.8%
7 3 3.947 4.532 14.8%
7 4 4.121 4.765 15.6%
7 5 4.815 5.375 11.6%
7 6 5.351 6.18 15.5%
11 0 4.244 4.581 7.9%
11 1 4.245 4.583 8.0%
11 2 4.244 4.592 8.2%
11 3 4.271 4.583 7.3%
11 4 4.245 4.602 8.4%
11 5 4.219 4.586 8.7%
11 6 4.243 4.587 8.1%
11 7 4.256 4.583 7.7%
11 8 4.764 5.066 6.3%
11 9 5.373 5.685 5.8%
11 10 6.189 6.609 6.8%
15 0 4.244 4.584 8.0%
15 1 4.244 4.558 7.4%
15 2 4.245 4.558 7.4%
15 3 4.243 4.584 8.0%
15 4 4.238 4.582 8.1%
15 5 4.239 4.579 8.0%
15 6 4.244 4.555 7.3%
15 7 4.242 4.579 7.9%
15 8 5.171 5.518 6.7%
15 9 5.149 5.495 6.7%
15 10 5.174 5.518 6.6%
15 11 5.147 5.513 7.1%
15 12 5.376 5.683 5.7%
15 13 6.186 6.61 6.9%
15 14 7.085 7.13 0.6%
19 0 5.156 4.618 -10.4%
19 1 5.157 4.592 -11.0%
19 2 5.157 4.615 -10.5%
19 3 5.156 4.619 -10.4%
19 4 5.151 4.629 -10.1%
19 5 5.155 4.615 -10.5%
19 6 5.159 4.593 -11.0%
19 7 5.16 4.62 -10.5%
19 8 5.387 4.966 -7.8%
19 9 5.372 4.963 -7.6%
19 10 5.371 4.938 -8.1%
19 11 5.368 4.96 -7.6%
19 12 5.367 4.961 -7.6%
19 13 5.372 4.961 -7.7%
19 14 5.371 4.882 -9.1%
19 15 5.373 4.964 -7.6%
19 16 5.368 4.875 -9.2%
19 17 5.994 5.43 -9.4%
19 18 6.858 6.058 -11.7%
23 0 5.183 4.616 -10.9%
23 1 5.161 4.62 -10.5%
23 2 5.156 4.616 -10.5%
23 3 5.158 4.61 -10.6%
23 4 5.159 4.548 -11.8%
23 5 5.156 4.618 -10.4%
23 6 5.159 4.62 -10.4%
23 7 5.158 4.548 -11.8%
23 8 5.366 4.962 -7.5%
23 9 5.369 4.96 -7.6%
23 10 5.365 4.964 -7.5%
23 11 5.37 4.965 -7.5%
23 12 5.368 4.935 -8.1%
23 13 5.368 4.937 -8.0%
23 14 5.366 4.958 -7.6%
23 15 5.368 4.974 -7.3%
23 16 6.307 5.583 -11.5%
23 17 6.323 5.585 -11.7%
23 18 6.296 5.558 -11.7%
23 19 6.313 5.584 -11.5%
23 20 6.358 5.664 -10.9%
23 21 7.171 6.448 -10.1%
23 22 7.669 7.227 -5.8%
27 0 5.196 4.668 -10.2%
27 1 5.195 4.655 -10.4%
27 2 5.193 4.664 -10.2%
27 3 5.194 4.669 -10.1%
27 4 5.192 4.638 -10.7%
27 5 5.193 4.666 -10.1%
27 6 5.193 4.663 -10.2%
27 7 5.194 4.682 -9.9%
27 8 5.41 5.011 -7.4%
27 9 5.412 5.011 -7.4%
27 10 5.41 5.008 -7.4%
27 11 5.411 5.011 -7.4%
27 12 5.41 5.013 -7.3%
27 13 5.412 5.016 -7.3%
27 14 5.409 5.019 -7.2%
27 15 5.41 5.012 -7.4%
27 16 6.336 5.627 -11.2%
27 17 6.356 5.627 -11.5%
27 18 6.358 5.618 -11.6%
27 19 6.357 5.622 -11.6%
27 20 6.36 5.623 -11.6%
27 21 6.361 5.622 -11.6%
27 22 6.356 5.623 -11.5%
27 23 6.388 5.65 -11.6%
27 24 6.859 6.01 -12.4%
27 25 7.356 6.887 -6.4%
27 26 8.02 7.43 -7.4%
31 0 5.17 4.638 -10.3%
31 1 5.194 4.67 -10.1%
31 2 5.187 4.671 -9.9%
31 3 5.186 4.666 -10.0%
31 4 5.198 4.671 -10.1%
31 5 5.194 4.663 -10.2%
31 6 5.19 4.67 -10.0%
31 7 5.194 4.666 -10.2%
31 8 5.414 5.019 -7.3%
31 9 5.417 5.018 -7.4%
31 10 5.414 5.038 -6.9%
31 11 5.418 4.992 -7.9%
31 12 5.415 5.014 -7.4%
31 13 5.389 5.037 -6.5%
31 14 5.417 5.019 -7.3%
31 15 5.414 5.018 -7.3%
31 16 6.356 5.596 -12.0%
31 17 6.351 5.63 -11.4%
31 18 6.355 5.631 -11.4%
31 19 6.363 5.63 -11.5%
31 20 6.359 5.634 -11.4%
31 21 6.337 5.606 -11.5%
31 22 6.351 5.631 -11.3%
31 23 6.356 5.603 -11.8%
31 24 7.387 6.852 -7.2%
31 25 7.38 6.87 -6.9%
31 26 7.359 6.872 -6.6%
31 27 7.387 6.867 -7.0%
31 28 7.385 6.895 -6.6%
31 29 8.063 7.429 -7.9%
31 30 8.544 8.075 -5.5%

This change makes performance worse for array sizes of 1-15 by as much as 14%. Array size of 16 benefits from the change by 11%. Roughly as the array gets bigger, the benefit gets smaller. For larger arrays, I tested up to size 1000, both versions perform about the same.

@zl-wang zl-wang left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

with the expectation that String length is on the short side (24bytes on average in UTF16; expecting 12bytes by and large in compressed), i can accept the pre-testing of 4/8.

@zl-wang

zl-wang commented Jan 10, 2025

Copy link
Copy Markdown
Contributor

@dsouzai please review/approve/merge

@dsouzai dsouzai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving based on @zl-wang's approval.

@dsouzai

dsouzai commented Jan 10, 2025

Copy link
Copy Markdown
Contributor

jenkins build plinux,aix

@dsouzai dsouzai self-assigned this Jan 10, 2025
@dsouzai

dsouzai commented Jan 10, 2025

Copy link
Copy Markdown
Contributor

@IBMJimmyk Bitwise/PPCTrg1Src2EncodingTest test failures; the new tests you added I believe.

[2025-01-10T20:12:15.809Z] 29: �[0;32m[----------] �[m54 tests from Bitwise/PPCTrg1Src2EncodingTest
[2025-01-10T20:12:15.809Z] 29: /home/omr/workspace/Build/fvtest/compilerunittest/p/BinaryEncoder.cpp:567: Failure
[2025-01-10T20:12:15.809Z] 29:       Expected: fixEndianness(std::get<4>(GetParam()))
[2025-01-10T20:12:15.809Z] 29:       Which is: [ 7f e0 03 f8 ]
[2025-01-10T20:12:15.809Z] 29: To be equal to: encodeInstruction(instr)
[2025-01-10T20:12:15.809Z] 29:       Which is: [ 7c 1f 03 f8 ]
[2025-01-10T20:12:15.809Z] 29: �[0;31m[  FAILED  ] �[mBitwise/PPCTrg1Src2EncodingTest.encode/24, where GetParam() = (cmpb, gr31, gr0, gr0, [ 7f e0 03 f8 ]) (0 ms)
[2025-01-10T20:12:15.809Z] 29: /home/omr/workspace/Build/fvtest/compilerunittest/p/BinaryEncoder.cpp:567: Failure
[2025-01-10T20:12:15.809Z] 29:       Expected: fixEndianness(std::get<4>(GetParam()))
[2025-01-10T20:12:15.809Z] 29:       Which is: [ 7c 1f 03 f8 ]
[2025-01-10T20:12:15.809Z] 29: To be equal to: encodeInstruction(instr)
[2025-01-10T20:12:15.809Z] 29:       Which is: [ 7f e0 03 f8 ]
[2025-01-10T20:12:15.809Z] 29: �[0;31m[  FAILED  ] �[mBitwise/PPCTrg1Src2EncodingTest.encode/25, where GetParam() = (cmpb, gr0, gr31, gr0, [ 7c 1f 03 f8 ]) (1 ms)
[2025-01-10T20:12:15.809Z] 29: �[0;32m[----------] �[m54 tests from Bitwise/PPCTrg1Src2EncodingTest (17 ms total)

@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

Let me take a look at those failures.

Modified both arraycmp and arraycmplen evaluators on Power 8 and Power 9
to take advantage of vector instructions. Power 10 already has an
existing vector implementation of arraycmp and continues to use it.

Added binary encoder tests for the newly used cmpb, vclzd and vclzw
instructions.

Signed-off-by: jimmyk <jimmyk@ca.ibm.com>
@IBMJimmyk

Copy link
Copy Markdown
Contributor Author

I ended up mixing up the expected results for two encoding tests. I fixed the problem so the tests should work now.

@dsouzai

dsouzai commented Jan 13, 2025

Copy link
Copy Markdown
Contributor

jenkins build plinux,aix

@dsouzai

dsouzai commented Jan 13, 2025

Copy link
Copy Markdown
Contributor

plinux failure is due to #6571

@dsouzai
dsouzai merged commit 2767da3 into eclipse-omr:master Jan 13, 2025
@IBMJimmyk
IBMJimmyk deleted the vectorArrayCmp branch January 13, 2025 18:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants