Dygresja na temat kodu maszynowego

"Piękne to nie jest, ale kogóż to obchodzi, skoro jest bardziej efektywne." Cóż, nie za bardzo. Jeżeli Czytelnik rozumie kod, zapisany w asemblerze x86, powinien przyjrzeć się bliżej wynikowi pracy 16-bitowego kompilatora optymalizującego, którym skompilowano obie implementacje funkcji StrLen

Wersja tablicowa Wersja wskaźnikowa
?StrLen@@YAHPAD@Z      PROC NEAR
      push      bp
      mov      bp,sp
      push     di
;     pStr = 4
;     register bx = i
      mov      di,WORD PTR [bp+4]
      xor      bx,bx
      cmp      BYTE PTR [di],bl
      je      $FB1596
$F1594:
      inc      bx
      cmp      BYTE PTR [bx][di],0
      jne      $F1594
$FB1596:
      mov      ax,bx
      pop      di

      mov      sp,bp
      pop      bp
      ret      
?StrLen@@YAHPAD@Z      ENDP
?StrLen@@YAHPAD@Z      PROC NEAR
      push     bp
      mov      bp,sp

;     register bx = p
;     pStr = 4
      mov      dx,WORD PTR [bp+4]
      mov      bx,dx


$FC1603:
      inc      bx
      cmp      BYTE PTR [bx-1],0
      jne      $FC1603

      mov      ax,bx
      sub     ax,dx
      dec     ax
      mov      sp,bp
      pop      bp
      ret      
?StrLen@@YAHPAD@Z      ENDP

W pierwszej implementacji kompilator umieścił w rejestrach procesora aż dwie zmienne, stąd dodatkowe instrukcje push i pop. W obu przypadkach mamy do czynienia z zasadniczo taką samą pętlą -- róznica polega jedynie na zastosowanym trybie adresowania. Bliższa analiza obu programów wykazuje, że użyta w drugiej pętli instrukcja jest o jeden bajt dłuższa od pierwszej.
80 39 00 cmp BYTE PTR [bx][di],0 ; pierwsza pętla
80 7f ff 00 cmp BYTE PTR [bx-1],0 ; druga pętla

Dlatego w przypadku bardzo długich napisów implementacja tablicowa powinna być lepsza, od implementacji wskaźnikowej. Ale czy tak jest naprawdę? Wiele zależy od sposobu wyrównywania instrukcji (ang. instruction alignment). Na moim starym komputerze (z procesorem i80486) druga instrukcja była lepiej wyrównana i dlatego była szybsza.

W reprezentacji wskaźnikowej dodatkowe instrukcje wykonywane są na końcu funkcji, a w wersji tablicowej -- na jej początku, przed pętlą, jednak sama pętla wykonywana jest o jeden raz mniej. Na moim komputerze narzut, związany z zastosowaniem reprezentacji tablicowej jest mniejszy, niż w przypadku reprezentacji wskaźnikowej. Dlatego w przypadku krótkich napisów (liczących do trzech znaków) reprezentacja tablicowa jest szybsza.

Prawdę mówiąc, ewentualne różnice nie mają większego znaczenia. Czy dla w sumie niewielkich i niepewnych oszczędności warto tak komplikować program? Czy czytelnik dokonał porównania instrukcji asemblera i prędkości wykonywania kodu dla wszystkich swoich ulubionych sztuczek? Może nadszedł już czas, by porzucić wszystkie te idiomy pionierów języka C. Może należy zastąpić je nowymi idiomami, których celem jest ułatwienie zrozumienia i konserwacji kodu. Chyba nie chcemy być jak ten chłopek-roztropek, który tak chciał zaoszczędzić grosik, że nawet nie spostrzegł się, gdy stracił złotówkę.

Nie używaj wskaźników tam, gdzie wystarczy operator [ ]