Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

fast way to check if an array of chars is zero [duplicate]

I have an array of bytes, in memory. What's the fastest way to see if all the bytes in the array are zero?

like image 618
Claudiu Avatar asked Apr 07 '10 02:04

Claudiu


4 Answers

Nowadays, short of using SIMD extensions (such as SSE on x86 processors), you might as well iterate over the array and compare each value to 0.

In the distant past, performing a comparison and conditional branch for each element in the array (in addition to the loop branch itself) would have been deemed expensive and, depending on how often (or early) you could expect a non-zero element to appear in the array, you might have elected to completely do without conditionals inside the loop, using solely bitwise-or to detect any set bits and deferring the actual check until after the loop completes:

int sum = 0; for (i = 0; i < ARRAY_SIZE; ++i) {   sum |= array[i]; } if (sum != 0) {   printf("At least one array element is non-zero\n"); } 

However, with today's pipelined super-scalar processor designs complete with branch prediction, all non-SSE approaches are virtualy indistinguishable within a loop. If anything, comparing each element to zero and breaking out of the loop early (as soon as the first non-zero element is encountered) could be, in the long run, more efficient than the sum |= array[i] approach (which always traverses the entire array) unless, that is, you expect your array to be almost always made up exclusively of zeroes (in which case making the sum |= array[i] approach truly branchless by using GCC's -funroll-loops could give you the better numbers -- see the numbers below for an Athlon processor, results may vary with processor model and manufacturer.)

#include <stdio.h>  int a[1024*1024];  /* Methods 1 & 2 are equivalent on x86 */    int main() {   int i, j, n;  # if defined METHOD3   int x; # endif    for (i = 0; i < 100; ++i) { #   if defined METHOD3     x = 0; #   endif     for (j = 0, n = 0; j < sizeof(a)/sizeof(a[0]); ++j) { #     if defined METHOD1       if (a[j] != 0) { n = 1; } #     elif defined METHOD2       n |= (a[j] != 0); #     elif defined METHOD3       x |= a[j]; #     endif     } #   if defined METHOD3     n = (x != 0); #   endif      printf("%d\n", n);   } }  $ uname -mp i686 athlon $ gcc -g -O3 -DMETHOD1 test.c $ time ./a.out real    0m0.376s user    0m0.373s sys     0m0.003s $ gcc -g -O3 -DMETHOD2 test.c $ time ./a.out real    0m0.377s user    0m0.372s sys     0m0.003s $ gcc -g -O3 -DMETHOD3 test.c $ time ./a.out real    0m0.376s user    0m0.373s sys     0m0.003s  $ gcc -g -O3 -DMETHOD1 -funroll-loops test.c $ time ./a.out real    0m0.351s user    0m0.348s sys     0m0.003s $ gcc -g -O3 -DMETHOD2 -funroll-loops test.c $ time ./a.out real    0m0.343s user    0m0.340s sys     0m0.003s $ gcc -g -O3 -DMETHOD3 -funroll-loops test.c $ time ./a.out real    0m0.209s user    0m0.206s sys     0m0.003s 
like image 156
vladr Avatar answered Sep 24 '22 15:09

vladr


Here's a short, quick solution, if you're okay with using inline assembly.

#include <stdio.h>  int main(void) {     int checkzero(char *string, int length);     char str1[] = "wow this is not zero!";     char str2[] = {0, 0, 0, 0, 0, 0, 0, 0};     printf("%d\n", checkzero(str1, sizeof(str1)));     printf("%d\n", checkzero(str2, sizeof(str2))); }  int checkzero(char *string, int length) {     int is_zero;     __asm__ (         "cld\n"         "xorb %%al, %%al\n"         "repz scasb\n"         : "=c" (is_zero)         : "c" (length), "D" (string)         : "eax", "cc"     );     return !is_zero; } 

In case you're unfamiliar with assembly, I'll explain what we do here: we store the length of the string in a register, and ask the processor to scan the string for a zero (we specify this by setting the lower 8 bits of the accumulator, namely %%al, to zero), reducing the value of said register on each iteration, until a non-zero byte is encountered. Now, if the string was all zeroes, the register, too, will be zero, since it was decremented length number of times. However, if a non-zero value was encountered, the "loop" that checked for zeroes terminated prematurely, and hence the register will not be zero. We then obtain the value of that register, and return its boolean negation.

Profiling this yielded the following results:

$ time or.exe  real    0m37.274s user    0m0.015s sys     0m0.000s   $ time scasb.exe  real    0m15.951s user    0m0.000s sys     0m0.046s 

(Both test cases ran 100000 times on arrays of size 100000. The or.exe code comes from Vlad's answer. Function calls were eliminated in both cases.)

like image 28
susmits Avatar answered Sep 22 '22 15:09

susmits


If you want to do this in 32-bit C, probably just loop over the array as a 32-bit integer array and compare it to 0, then make sure the stuff at the end is also 0.

like image 39
WhirlWind Avatar answered Sep 23 '22 15:09

WhirlWind


If the array is of any decent size, your limiting factor on a modern CPU is going to be access to the memory.

Make sure to use cache prefetching for a decent distance ahead (i.e. 1-2K) with something like __dcbt or prefetchnta (or prefetch0 if you are going to use the buffer again soon).

You will also want to do something like SIMD or SWAR to or multiple bytes at a time. Even with 32-bit words, it will be 4X less operations than a per character version. I'd recommend unrolling the or's and making them feed into a "tree" of or's. You can see what I mean in my code example - this takes advantage of superscalar capability to do two integer ops (the or's) in parallel by making use of ops that do not have as many intermediate data dependencies. I use a tree size of 8 (4x4, then 2x2, then 1x1) but you can expand that to a larger number depending on how many free registers you have in your CPU architecture.

The following pseudo-code example for the inner loop (no prolog/epilog) uses 32-bit ints but you could do 64/128-bit with MMX/SSE or whatever is available to you. This will be fairly fast if you have prefetched the block into the cache. Also you will possibly need to do unaligned check before if your buffer is not 4-byte aligned and after if your buffer (after alignment) is not a multiple of 32-bytes in length.

const UINT32 *pmem = ***aligned-buffer-pointer***;

UINT32 a0,a1,a2,a3;
while(bytesremain >= 32)
{
    // Compare an aligned "line" of 32-bytes
    a0 = pmem[0] | pmem[1];
    a1 = pmem[2] | pmem[3];
    a2 = pmem[4] | pmem[5];
    a3 = pmem[6] | pmem[7];
    a0 |= a1; a2 |= a3;
    pmem += 8;
    a0 |= a2;
    bytesremain -= 32;
    if(a0 != 0) break;
}

if(a0!=0) then ***buffer-is-not-all-zeros***

I would actually suggest encapsulating the compare of a "line" of values into a single function and then unrolling that a couple times with the cache prefetching.

like image 35
Adisak Avatar answered Sep 22 '22 15:09

Adisak