Avatar billede soreno Praktikant
07. august 2003 - 20:17 Der er 26 kommentarer og
1 løsning

analyse af kodestump

Kig på følgende kode:

static const unsigned long magic = 0x01010101;

size_t strlen(const char *s)
{
  const char *t = s;
  unsigned long word;

  if (!s) return 0;

  /* Byte compare up until word boundary */
  for (; ((unsigned long) t & 3); t++)
    if (!*t) return t - s;

  /* Word compare */
  do {
    word = *((unsigned long *) t); t += 4;
    word = (word - magic) &~ word;
    word &= (magic << 7);
  } while (word == 0);

#if BYTE_ORDER == LITTLE_ENDIAN
  /* word & 0x80808080 == word */
  word = (word - 1) & (magic << 10);
  word += (word << 8) + (word << 16);
  t += word >> 26;
#else
  if ((word & 0x80800000) == 0) {
    word <<= 16;
    t += 2;
  }
  if ((word & 0x80000000) == 0) t += 1;
#endif
  return ((const char *) t) - 4 - s;
}


Hvad er det lige der sker - er der mening med galskaben ?
Er der nogen der kan forklare koden i hovedtræk ?

Koden er en del af "dietlibc-0.22"
Avatar billede tosssen Nybegynder
07. august 2003 - 20:37 #1
Jeg lytter lige med - det virker pænt indviklet ;-)
Avatar billede soreno Praktikant
07. august 2003 - 20:44 #2
Ja, tænk at gøre det så besværligt at tælle længden på et char array.

Vedligeholdelsesvenligt er det ihvertfald ikke !
:-)
Avatar billede tosssen Nybegynder
07. august 2003 - 22:01 #3
Ja man skulle tro at man bare kunne tælle mens man løber det igennem indtil man støder på '\0'
Avatar billede tosssen Nybegynder
07. august 2003 - 22:01 #4
Det må være en virkelig optimeret algoritme
Avatar billede arne_v Ekspert
07. august 2003 - 22:12 #5
Den er faktisk rimelig hurtig.

Kode:

#include <stdlib.h>

static const unsigned long magic = 0x01010101;

size_t strlen1(const char *s)
{
  const char *t = s;
  unsigned long word;

  if (!s) return 0;

  /* Byte compare up until word boundary */
  for (; ((unsigned long) t & 3); t++)
    if (!*t) return t - s;

  /* Word compare */
  do {
    word = *((unsigned long *) t); t += 4;
    word = (word - magic) &~ word;
    word &= (magic << 7);
  } while (word == 0);

#if BYTE_ORDER == LITTLE_ENDIAN
  /* word & 0x80808080 == word */
  word = (word - 1) & (magic << 10);
  word += (word << 8) + (word << 16);
  t += word >> 26;
#else
  if ((word & 0x80800000) == 0) {
    word <<= 16;
    t += 2;
  }
  if ((word & 0x80000000) == 0) t += 1;
#endif
  return ((const char *) t) - 4 - s;
}

size_t strlen2(const char *s)
{
  int ix = 0;
  while(s[ix]) ix++;
  return ix;
}

size_t strlen3(const char *s)
{
  char *p = (char *)s;
  while(*p) p++;
  return (p-s);
}

#include <stdio.h>
#include <string.h>
#include <time.h>

#define N 100000000

int main()
{
  int i;
  time_t t1,t2;
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen("abcdefgijhklmopqrstuvwxyz0123456789");
  t2 = time(NULL);
  printf("builtin %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen1("abcdefgijhklmopqrstuvwxyz0123456789");
  t2 = time(NULL);
  printf("special %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen2("abcdefgijhklmopqrstuvwxyz0123456789");
  t2 = time(NULL);
  printf("simple array %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen3("abcdefgijhklmopqrstuvwxyz0123456789");
  t2 = time(NULL);
  printf("simple pointer %d\n",t2-t1);
  return 0;
}

output:

builtin 1
special 8
simple array 26
simple pointer 23
Avatar billede arne_v Ekspert
07. august 2003 - 22:16 #6
Det der gør den hurtig er at den minimerer tilgangen til RAM.

De to simple funktioner jeg laver henter en byte af gangen fra memory
og skal derfor have fat i memory X gange.

Den specielle henter longs fra memory og skal derfor kun hente fra
memory X/4 gange.

[word kører rundt i registre]
Avatar billede tosssen Nybegynder
07. august 2003 - 22:25 #7
Sejt nok! Nu mangler vi bare at forstå algoritmen ;-)
Avatar billede arne_v Ekspert
07. august 2003 - 22:28 #8
Til at illustere min pointe så har jeg lavet en ny variant:

#include <stdlib.h>

static const unsigned long magic = 0x01010101;

size_t strlen1(const char *s)
{
  const char *t = s;
  unsigned long word;

  if (!s) return 0;

  /* Byte compare up until word boundary */
  for (; ((unsigned long) t & 3); t++)
    if (!*t) return t - s;

  /* Word compare */
  do {
    word = *((unsigned long *) t); t += 4;
    word = (word - magic) &~ word;
    word &= (magic << 7);
  } while (word == 0);

#if BYTE_ORDER == LITTLE_ENDIAN
  /* word & 0x80808080 == word */
  word = (word - 1) & (magic << 10);
  word += (word << 8) + (word << 16);
  t += word >> 26;
#else
  if ((word & 0x80800000) == 0) {
    word <<= 16;
    t += 2;
  }
  if ((word & 0x80000000) == 0) t += 1;
#endif
  return ((const char *) t) - 4 - s;
}

size_t strlen2(const char *s)
{
  int ix = 0;
  while(s[ix]) ix++;
  return ix;
}

size_t strlen3(const char *s)
{
  char *p = (char *)s;
  while(*p) p++;
  return (p-s);
}

size_t strlen4(const char *s)
{
  char *p = (char *)s;
  long l;
  while(((int)p)%4!=0)
  {
      if(!*p) return (p-s);
      p++;
  }
  for(;;) {
      l = *(long *)p;
      if(!(l & 0x000000FF)) return (p-s);
      if(!(l & 0x0000FF00)) return (p-s+1);
      if(!(l & 0x00FF0000)) return (p-s+2);
      if(!(l & 0xFF000000)) return (p-s+3);
      p+=4;
  }
}

#include <stdio.h>
#include <string.h>
#include <time.h>

#define N 100000000
#define S "abcdefgijhklmopqrstuvwxyz0123456789"

int main()
{
  int i;
  time_t t1,t2;
  printf("%d\n",strlen(S));
  printf("%d\n",strlen1(S));
  printf("%d\n",strlen2(S));
  printf("%d\n",strlen3(S));
  printf("%d\n",strlen4(S));
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen(S);
  t2 = time(NULL);
  printf("builtin %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen1(S);
  t2 = time(NULL);
  printf("special %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen2(S);
  t2 = time(NULL);
  printf("simple array %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen3(S);
  t2 = time(NULL);
  printf("simple pointer %d\n",t2-t1);
  t1 = time(NULL);
  for(i=0;i<N;i++) strlen4(S);
  t2 = time(NULL);
  printf("my special %d\n",t2-t1);
  return 0;
}

output:

35
35
35
35
35
builtin 0
special 9
simple array 26
simple pointer 21
my special 11
Avatar billede arne_v Ekspert
07. august 2003 - 22:30 #9
my special begynder at ligne special en lille smule i kode og
en masse i performance.
Avatar billede tosssen Nybegynder
07. august 2003 - 22:35 #10
Imponerende nok - det kræver vidst lidt nærmere kendskab til maskinarkitektur...
Avatar billede arne_v Ekspert
07. august 2003 - 22:41 #11
Jeg har stadigvæk ikke forklaret hvad de laver med 0x01010101 ...
Avatar billede arne_v Ekspert
07. august 2003 - 22:58 #12
if (!s) return 0;

fanger den tomme streng.

  for (; ((unsigned long) t & 3); t++)
    if (!*t) return t - s;

tester 0-3 bytes op til en adresse der er et multipla af 4.

  do {
    word = *((unsigned long *) t); t += 4;
    word = (word - magic) &~ word;
    word &= (magic << 7);
  } while (word == 0);

processer 4 byte ad gange så længe der ikke er nogen nul bytes
i den pågældende long.

#include <stdio.h>

void test(int v)
{
  int v2 = v;
  v2 = (v2 - 0x01010101) & ~v2;
  v2 = v2 & 0x80808080;
  printf("%08X %08X\n",v,v2);
  return;
}

int main()
{
  test(0x12345678);
  test(0x00345678);
  test(0x12345600);
  return 0;
}

outputter:

12345678 00000000
00345678 80000000
12345600 00000080
Avatar billede arne_v Ekspert
07. august 2003 - 23:03 #13
word = (word - 1) & (magic << 10);
  word += (word << 8) + (word << 16);
  t += word >> 26;

udregner position af den nul byte.

#include <stdio.h>

void test(int v)
{
  int offset;
  int v2 = v;
  v2 = (v2 - 0x01010101) & ~v2;
  v2 = v2 & 0x80808080;
  printf("%08X %08X\n",v,v2);
  if(v2!=0)
  {
      offset = (v2 - 1) & 0x04040400;
      offset = offset + (offset << 8) + (offset << 16);
      offset = offset >> 26;
      printf("%d\n",offset);
  }
  return;
}

int main()
{
  test(0x12345678);
  test(0x00345678);
  test(0x12005678);
  test(0x12340078);
  test(0x12345600);
  return 0;
}

outputter:

12345678 00000000
00345678 80000000
3
12005678 00800000
2
12340078 00008000
1
12345600 00000080
0
Avatar billede arne_v Ekspert
07. august 2003 - 23:04 #14
Men at forklare de konstanter og matematiske logik ligger vist udover
mine matematiske evner.
Avatar billede bertelbrander Novice
07. august 2003 - 23:06 #15
Hvis vi nøjes med at se på en unsigned char kan det ret let vises at der for:
(word - 1) &~ word;
kun er en værdi af word der resulterer i at MSB (bit 7) er sat i resultatet, denne værdi er 0.
Linien  word &= (magic << 7) tester efterfølgende for om MSB er sat.
Avatar billede bertelbrander Novice
07. august 2003 - 23:07 #16
At det netop er 0 der resulterer i at MSB er sat i resultatet, skyldes at der for 0 sker en "overflow" når man trækker 1 fra.
Avatar billede soreno Praktikant
08. august 2003 - 09:12 #18
Finno - så var der mening med galskaben !

:-)

Jeg savner nogle svar

Så hvis Arnelog Bertel lige vil være så venlig ?
Avatar billede arne_v Ekspert
08. august 2003 - 09:19 #19
svar
Avatar billede arne_v Ekspert
08. august 2003 - 09:56 #20
Det er værd at bemærke at sådan noget er meget CPU og compiler specifikt.

Mine resulateter fra i aftes er på en Athlon XP 2000+ uden specielle
switches.

På en 750 MHz Pemntium III for jeg uden specielle switches:

builtin 1
special 27
simple array 38
simple pointer 37
my special 38

men med -O6 får jeg:

builtin 1
special 6
simple array 12
simple pointer 11
my special 10
Avatar billede soreno Praktikant
08. august 2003 - 10:29 #21
Det kunne egentlig være sjovt at se hvordan den indbyggede metode er kodet !

Den må ligge i msvcrt.dll (har du ikke brugt Windows?) - men det kan man jo ikke lige umiddelbart se koden til.

Hvad med på en Linux maskine - hvad giver den (jeg har ikke lige adgang til en med compiler på) ?
Avatar billede arne_v Ekspert
08. august 2003 - 10:38 #22
Den indbyggede bruger formentlig en processor specifik instruktion !

Operativ-systemet burde ikke netyde noget for denne her problem-stilling.

GCC (som jeg har brugt) burde give samme resultat på en given
CPU uanset om den kører Windows eller Linux.
Avatar billede arne_v Ekspert
08. august 2003 - 11:16 #23
Mere specifikt vil man på x86 bruge:
  repne scasb
se mere her:
  http://www.int80h.org/strlen/
Avatar billede soreno Praktikant
08. august 2003 - 11:59 #24
Det var det jeg var ude efter - altså hvordan den indbyggede funktion fungerer.

Det kunne være sjovt at få den asm instruktion til at spille..

Men så skal der jo konverteres fra Intel -> AT&T syntaks. Det kræver vist et større research arbejde at få til at virke (jeg kan få det til at compile - men det virker ikke).
Avatar billede arne_v Ekspert
08. august 2003 - 12:13 #25
Avatar billede soreno Praktikant
08. august 2003 - 13:10 #26
Det ser spændende ud.

Men du har ret - et farveladekursus ville gøre godt for ham som har sammensat de farver !

Det vil jeg kigge lidt nærmere på de kommende dage - hvis jeg får tid..

Tak for hjælpen.
Avatar billede arne_v Ekspert
10. august 2003 - 22:01 #27
Lidt flere resultater:

Athlon XP 2000+ med -O6:

builtin 0
special 2
simple array 6
simple pointer 5
my special 3

Alpha 433 MHz no switches:

builtin 0
special 19
simple array 30
simple pointer 31
my special 21

Alpha 433 MHz with /opt=tune=ev56:

builtin 0
special 19
simple array 22
simple pointer 22
my special 19

Pentium 200 MHz (scaled with 1/10) no switches:

builtin 1
special 13
simple array 21
simple pointer 18
my special 11

Pentium 200 MHz (scaled with 1/10) med -O6:

builtin 0
special 4
simple array 7
simple pointer 7
my special 5
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview
Kategori
Kurser inden for grundlæggende programmering

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester