Desenvolvimento

6 abr, 2017

Como rodar um sistema de 32 bits em uma CPU de 64 bits

Publicidade

Vira e mexe aparece esse assunto em algum grupo de discussão, seja no Facebook, ou no Telegram. São vários os relatos de instalação de sistemas de 32 bits em máquinas mais modestas, em geral com até 2 GB de RAM, e que ficaram mais rápidas.

Então, resolvi passar um tempo fazendo a prova, inclusive da quantidade de memória.

Sistema de testes e validação:

Pra validar, instalei em VMs (VirtualBox) dois sistemas Ubuntu baseados no 16.04, Xenial. Ambos configurados como CPUs de 64 bits, 2 GB de RAM, 128 MB de vídeo com aceleração 3D e 20 GB de disco.

Para consumir memória, deixei ambos com o navegador firefox rodando no tweetdeck, aplicativo que mostra o fluxo de mensagens do Twitter. Como o mesmo é feito em JavaScript, vai consumindo a memória conforme o tempo passa. Ambos começaram com frugais 300 MB de RAM e, ao terminar de escrever, depois de mais de 24 horas rodando, ambos consumiam mais de 1.5 GB da RAM.

Não fiz testes de desempenho pelo motivo óbvio: fiz com meu laptop e ele não é exatamente um tipo de ambiente dos mais robustos para esse tipo de coisa. Mas para finalidade de verificação de tamanho de binários em máquinas virtuais, serve bem.

Controle da missão

Cada VM recebeu o nome de “UbuntuXX”, onde XX é a arquitetura do software, 32 ou 64 bits.

Memória consumida

Logo de início, o sistema com 32 bits mostra mais memória que realmente disponível. Ambas as VMs com 2 GB de RAM, mas com 32 bits, ele mostra um pouco mais de 2048 MB. Bug? Não sei.

root@ubuntu64:~# head -5 /proc/meminfo
MemTotal:        2048524 kB
MemFree:          102900 kB
MemAvailable:     859980 kB
Buffers:          132452 kB
Cached:           541576 kB

root@ubuntu32:~# head -5 /proc/meminfo 
MemTotal:        2060988 kB
MemFree:          380360 kB
MemAvailable:     507600 kB
Buffers:           58812 kB
Cached:           272512 kB

E na configuração das VMS…

helio@XPS13:VirtualBox VMs$ grep RAMSize Ubuntu\ {32,64}/Ubuntu\ {32,64}.vbox
Ubuntu 32/Ubuntu 32.vbox:      <Memory RAMSize="2048"/>
Ubuntu 32/Ubuntu 32.vbox:      <Display VRAMSize="128" accelerate3D="true"/>
Ubuntu 64/Ubuntu 64.vbox:      <Memory RAMSize="2048"/>
Ubuntu 64/Ubuntu 64.vbox:      <Display VRAMSize="128" accelerate3D="true"/>

Uma olhada no sistema usando top:

root@ubuntu64:~# top -b -n 1 | head -5
top - 18:42:55 up  2:38,  2 users,  load average: 0,01, 0,02, 0,05
Tasks: 178 total,   1 running, 177 sleeping,   0 stopped,   0 zombie
%Cpu(s):  5,0 us,  0,8 sy,  0,1 ni, 93,8 id,  0,1 wa,  0,0 hi,  0,1 si,  0,0 st
KiB Mem :  2048524 total,    47004 free,  1157408 used,   844112 buff/cache
KiB Swap:  2095100 total,  2082632 free,    12468 used.   842436 avail Mem 

root@ubuntu32:~# top -b -n 1 | head -5
top - 18:42:45 up  2:38,  2 users,  load average: 0,00, 0,00, 0,00
Tasks: 171 total,   1 running, 170 sleeping,   0 stopped,   0 zombie
%Cpu(s):  9,3 us,  3,2 sy,  0,1 ni, 87,1 id,  0,1 wa,  0,0 hi,  0,2 si,  0,0 st
KiB Mem :  2060988 total,   256196 free,  1112400 used,   692392 buff/cache
KiB Swap:  2095100 total,  2089776 free,     5324 used.   681120 avail Mem

Como pode ser visto, o sistema de 32 bits mostra mais memória livre (256 MB contra 47 MB), menos uso de swap (5 MB contra 12 MB), mas tem menor memória disponível (681 MB contra 842 MB).

Outro ponto interessante é que são 177 processos rodando em 64 bits contra 170 em 32. E antes de perguntarem, não investiguei o motivo e não acho que realmente importe muito. Mas percebi que por algum motivo acabei com versões de kernel diferentes:

root@ubuntu64:~# uname -a
Linux ubuntu64 4.4.0-21-generic #37-Ubuntu SMP Mon Apr 18 18:33:37 UTC 2016 x86_64 x86_64 x86_64 GNU/Linux

root@ubuntu32:~# uname -a
Linux ubuntu32 4.8.0-45-generic #48~16.04.1-Ubuntu SMP Fri Mar 24 12:47:29 UTC 2017 i686 i686 i686 GNU/Linux

Isso tem importância na forma em que o kernel reserva a memória mas não usa. Fora isso, o resto é o mesmo.

Consumo de memória com o comando top ou htop

É bastante comum a verificação de memória utilizada pelo comando top, htop ou mesmo ps. É possível ver uma saída mais ou menos como essa com o top:

top - 12:58:22 up 20:54,  2 users,  load average: 1,08, 0,43, 0,19
Tasks: 184 total,   1 running, 183 sleeping,   0 stopped,   0 zombie
%Cpu(s):  3,5 us,  0,5 sy,  0,1 ni, 95,6 id,  0,1 wa,  0,0 hi,  0,1 si,  0,0 st
KiB Mem :  2048524 total,   106008 free,  1134092 used,   808424 buff/cache
KiB Swap:  2095100 total,  2017780 free,    77320 used.   860776 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S %CPU %MEM     TIME+ COMMAND
 1241 helio     20   0 1254076 160784  10380 S 50,0  7,8  30:33.19 compiz
  767 root      20   0  480156  88992   8388 S 25,0  4,3  15:18.11 Xorg
 2272 helio     20   0 1539028 451060  50504 S  6,2 22,0  42:32.37 firefox
    1 root      20   0  185720   5708   3340 S  0,0  0,3   0:03.14 systemd
    2 root      20   0       0      0      0 S  0,0  0,0   0:00.00 kthreadd
    3 root      20   0       0      0      0 S  0,0  0,0   0:01.38 ksoftirqd/0
    5 root       0 -20       0      0      0 S  0,0  0,0   0:00.00 kworker/0:0H
    7 root      20   0       0      0      0 S  0,0  0,0   0:01.65 rcu_sched
    8 root      20   0       0      0      0 S  0,0  0,0   0:00.00 rcu_bh
    9 root      rt   0       0      0      0 S  0,0  0,0   0:00.00 migration/0
   10 root      rt   0       0      0      0 S  0,0  0,0   0:00.38 watchdog/0
   11 root      20   0       0      0      0 S  0,0  0,0   0:00.00 kdevtmpfs

O comando top mostra o consumo de memória em VIRT, RES e SHR. É possível ver o Firefox consumindo 1.54 GB de memória VIRT, 451 MB de memória RES e 50 MB de memória SHR. Mas o que são?

Atualmente, é muito difícil um programa tão grande quanto o Firefox ter todas as bibliotecas incluídas em seu binário. Esse formato é chamado de estático, se usado. Isso faria com que o executável ficasse gigantesco. Fora isso, cada executável traria dentro de si a mesma biblioteca com os mesmos conteúdos. E a cada atualização dessas bibliotecas seria necessário alterar o executável também.

Pra melhorar a eficiência, binários criados no formato ELF (Executable and Linkable Format) têm uma parte do cabeçalho que informa onde buscar essas bibliotecas de funções, que são os arquivos .so. São chamadas as bibliotecas dinâmicas, que podem ser atualizadas com o executável rodando. Quando um binário ELF é executado, o kernel carrega seu código na memória e de suas bibliotecas de funções. Se outro binário que aponta pra mesma biblioteca é executado, o kernel não carrega novamente a biblioteca, mas aponta para o endereçamento de memória onde essa já existe.

Então, a memória VIRT, de virtual, mostra o total que um binário carregou na memória, inclusive com as bibliotecas dinâmicas que são compartilhadas. Esse tamanho, então, não é exatamente o que aquele programa sozinho carrega.

A memória RES mostra o quanto de memória está na RAM e não no SWAP. Mas ela mostra o total, incluindo das bibliotecas dinâmicas que são compartilhadas com outros programas.

A memória SHR é compartilhada, de quanto de memória é compartilhado com bibliotecas dinâmicas.

O resultado é que pra saber o quanto um programa está consumindo por si só, seria algo como VIRT menos a SHR. Daí, seria possível ver quanto o executável ocupa de memória, mais os stack de dados. Só que fica a pergunta: e a parte compartilhada, não conta?

Conta! É memória consumida. É RAM que não pode usar. Mas como fazer isso? Se o Firefox e o Unity, como exemplo, compartilharem uma biblioteca de renderização 3D, então, a gente diz que cada um consumiu metade dessa memória alocada? E se tiver mais um terceiro, ou quarto ou muito mais programas compartilhando essa mesma biblioteca? A resposta é tão complicada que é esse o motivo que o programa top não tenta resolver isso e mostra o total alocado, mesmo sabendo que boa parte daquilo é compartilhado.

Algumas formas de ver o quanto cada programa ocupa em stack de memória sem as bibliotecas compartilhadas são os comandos pmap e readelf, que serão usados mais adiante.

Uso de memória do firefox

Vamos começar pelo tamanho dos binários. O binário de 32 bits é muito menor que o de 64 bits?

root@ubuntu64:~# ls -l /usr/lib/firefox/firefox
-rwxr-xr-x 1 root root 125152 mar 29 18:25 /usr/lib/firefox/firefox
root@ubuntu32:~# ls -l /usr/lib/firefox/firefox
-rwxr-xr-x 1 root root 136664 mar 29 18:50 /usr/lib/firefox/firefox

E já de cara as coisas começam a ficar mais interessantes. O binário do Firefox em 32 bits é maior que o de 64 bits. Usando o comando readelf -S, é possível olhar o conteúdo de ambos e quanto de memória ocupa cada parte. Mas vou evitar colocar aqui o resultado inteiro, pois é muito longo e não será fácil visualizar as diferenças. Então, mostro apenas as partes diferentes de ambos. Os valores estão em hexadecimal:

	       32 bits 64 bits
.gnu.hash	04d4	04c4
.dynsym	        0ea0    1578
.dynstr	        184f    17c8
.gnu.version	01d4	01ca
.gnu.version_r	01a0	0160
.rel.dyn	0468	0cd8
init	        0023    001a
.plt.got	02b0	02a0
.text	        013b34	0123ff

Não completei a tablet toda, mas já dá pra perceber que os valores em 64 bits são menores em geral. Antes de uma pergunta sobre o motivo, isso eu não sei. Talvez algo em relação com compilador.

E quanto estão ocupando de memória rodando? Isso fica fácil verificar com o comando pmap.

root@ubuntu64:~# pmap  2272  | grep total
  total          1548796K
root@ubuntu32:~# pmap 2334 | grep total
 total   959916K

E o Firefox ocupa 1.55 GB de RAM em 64 bits, contra 956 MB em 32. Alguns já começam a se exaltar e gritar “viu! eu falei!”, mas (sempre tem um mas) em 32 bits, não sei a razão disso, o Firefox usa um plugin-container junto. Ao matar esse plugin-container…

então, ele tem de contar com o consumo de memória do Firefox como um todo. Sendo assim:

root@ubuntu32:~# ps auxwww | grep -i firefox
helio     2334  1.3 11.5 959920 238924 ?       Sl   apr01  17:44 /usr/lib/firefox/firefox
helio     2490  3.6 19.6 1102860 404644 ?      Sl   apr01  47:30 /usr/lib/firefox/plugin-container -greomni /usr/lib/firefox/omni.ja -appomni /usr/lib/firefox/browser/omni.ja -appdir /usr/lib/firefox/browser 2334 true tab
root      9461  0.0  0.0   6644   840 pts/4    S+   13:42   0:00 grep --color=auto -i firefox
root@ubuntu32:~# pmap 2334 | grep total
 total   959916K
root@ubuntu32:~# pmap 2490 | grep total
 total   996360K

O total de uso de memória em 32 bits é na verdade 1956276K, 1.96 GB de RAM.

O consumo de memória é mais otimizado em 64 bits. O esperado resultado de 32 bits ocupar menos memória não passa de uma lenda urbana.

Fazendo nossos próprios binários

Mas podemos fazer algo mais interessante e olhar à fundo as diferenças de 32 bits e 64 bits em arquiteturas de 64 bits. Pra isso, eu criei um pequeno programa em C, que basicamente cria uma string com meu nome e copia para outra variável. E fica fazendo isso. O motivo é apenas fazer análise de uso de memória com readelf e pmap.

testing.c:

#include
#include
#include

char nome[] = "Helio Loureiro";

int main() {
    char tmp[sizeof(nome)];
    while(1) {
        sleep(30);
        strcpy(tmp, nome);
   }
}

Compilando e verificando os tamanhos dos binários:

root@ubuntu64:~# vi testing.c
root@ubuntu64:~# gcc -o testing testing.c
root@ubuntu64:~# ls -l testing
-rwxr-xr-x 1 root root 8696 apr  1 18:22 testing

root@ubuntu32:~# vi testing.c
root@ubuntu32:~# gcc -o testing testing.c
root@ubuntu32:~# ls -l testing
-rwxr-xr-x 1 root root 7424 apr  1 18:22 testing

Nesse caso bem simples, com nenhuma biblioteca dinâmica em uso além da própria GNU libc, o executável em 32 bits ficou menor. Não metade do tamanho, mas 14% menor. E rodando?

root@ubuntu64:~# ./testing &
[1] 19892
root@ubuntu64:~# pmap -x 19892
19892:   ./testing
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000       4       4       0 r-x-- testing
0000000000400000       0       0       0 r-x-- testing
0000000000600000       4       4       4 r---- testing
0000000000600000       0       0       0 r---- testing
0000000000601000       4       4       4 rw--- testing
0000000000601000       0       0       0 rw--- testing
00007f21a0044000    1788     816       0 r-x-- libc-2.23.so
00007f21a0044000       0       0       0 r-x-- libc-2.23.so
00007f21a0203000    2048       0       0 ----- libc-2.23.so
00007f21a0203000       0       0       0 ----- libc-2.23.so
00007f21a0403000      16      16      16 r---- libc-2.23.so
00007f21a0403000       0       0       0 r---- libc-2.23.so
00007f21a0407000       8       8       8 rw--- libc-2.23.so
00007f21a0407000       0       0       0 rw--- libc-2.23.so
00007f21a0409000      16       8       8 rw---   [ anon ]
00007f21a0409000       0       0       0 rw---   [ anon ]
00007f21a040d000     152     140       0 r-x-- ld-2.23.so
00007f21a040d000       0       0       0 r-x-- ld-2.23.so
00007f21a0615000      12      12      12 rw---   [ anon ]
00007f21a0615000       0       0       0 rw---   [ anon ]
00007f21a0630000       8       8       8 rw---   [ anon ]
00007f21a0630000       0       0       0 rw---   [ anon ]
00007f21a0632000       4       4       4 r---- ld-2.23.so
00007f21a0632000       0       0       0 r---- ld-2.23.so
00007f21a0633000       4       4       4 rw--- ld-2.23.so
00007f21a0633000       0       0       0 rw--- ld-2.23.so
00007f21a0634000       4       4       4 rw---   [ anon ]
00007f21a0634000       0       0       0 rw---   [ anon ]
00007ffd44054000     132      12      12 rw---   [ stack ]
00007ffd44054000       0       0       0 rw---   [ stack ]
00007ffd440e3000       8       0       0 r----   [ anon ]
00007ffd440e3000       0       0       0 r----   [ anon ]
00007ffd440e5000       8       4       0 r-x--   [ anon ]
00007ffd440e5000       0       0       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
ffffffffff600000       0       0       0 r-x--   [ anon ]
---------------- ------- ------- ------- 
total kB            4224    1048      84

root@ubuntu32:~# ./testing &
[1] 9589
root@ubuntu32:~# pmap -x 9589
9589:   ./testing
Address   Kbytes     RSS   Dirty Mode  Mapping
08048000       4       4       0 r-x-- testing
08048000       0       0       0 r-x-- testing
08049000       4       4       4 r---- testing
08049000       0       0       0 r---- testing
0804a000       4       4       4 rw--- testing
0804a000       0       0       0 rw--- testing
b75d7000    1724     732       0 r-x-- libc-2.23.so
b75d7000       0       0       0 r-x-- libc-2.23.so
b7786000       4       0       0 ----- libc-2.23.so
b7786000       0       0       0 ----- libc-2.23.so
b7787000       8       8       8 r---- libc-2.23.so
b7787000       0       0       0 r---- libc-2.23.so
b7789000       4       4       4 rw--- libc-2.23.so
b7789000       0       0       0 rw--- libc-2.23.so
b778a000      12       8       8 rw---   [ anon ]
b778a000       0       0       0 rw---   [ anon ]
b77a5000       8       8       8 rw---   [ anon ]
b77a5000       0       0       0 rw---   [ anon ]
b77a7000       8       0       0 r----   [ anon ]
b77a7000       0       0       0 r----   [ anon ]
b77a9000       8       4       0 r-x--   [ anon ]
b77a9000       0       0       0 r-x--   [ anon ]
b77ab000     136     136       0 r-x-- ld-2.23.so
b77ab000       0       0       0 r-x-- ld-2.23.so
b77cd000       4       4       4 rw---   [ anon ]
b77cd000       0       0       0 rw---   [ anon ]
b77ce000       4       4       4 r---- ld-2.23.so
b77ce000       0       0       0 r---- ld-2.23.so
b77cf000       4       4       4 rw--- ld-2.23.so
b77cf000       0       0       0 rw--- ld-2.23.so
bfe72000     132       8       8 rw---   [ stack ]
bfe72000       0       0       0 rw---   [ stack ]
-------- ------- ------- ------- 
total kB    2068     932      56

É possível ver que em 64 bits o programa ocupa o dobro de memória em RAM que 32 bits. São 4224 KB contra 2068 KB. E é possível ver que é causado pela libc em 64 bits. Essa memória é totalmente usada pelo programa? Não, uma vez que a libc é compartilhada. Mas em 64 bits ela faz duas chamadas para si. O restante, como stack, os valores são exatamente os mesmos.

Olhando com readelf pra investigar o tamanho de cada parte do binário:

root@ubuntu64:~# readelf -S -W testing
There are 31 section headers, starting at offset 0x1a40:

Section Headers:
  [Nr] Name              Type            Address          Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            0000000000000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        0000000000400238 000238 00001c 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            0000000000400254 000254 000020 00   A  0   0  4
  [ 3] .note.gnu.build-id NOTE            0000000000400274 000274 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        0000000000400298 000298 00001c 00   A  5   0  8
  [ 5] .dynsym           DYNSYM          00000000004002b8 0002b8 000078 18   A  6   1  8
  [ 6] .dynstr           STRTAB          0000000000400330 000330 000045 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0000000000400376 000376 00000a 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0000000000400380 000380 000020 00   A  6   1  8
  [ 9] .rela.dyn         RELA            00000000004003a0 0003a0 000018 18   A  5   0  8
  [10] .rela.plt         RELA            00000000004003b8 0003b8 000048 18  AI  5  24  8
  [11] .init             PROGBITS        0000000000400400 000400 00001a 00  AX  0   0  4
  [12] .plt              PROGBITS        0000000000400420 000420 000040 10  AX  0   0 16
  [13] .plt.got          PROGBITS        0000000000400460 000460 000008 00  AX  0   0  8
  [14] .text             PROGBITS        0000000000400470 000470 0001a2 00  AX  0   0 16
  [15] .fini             PROGBITS        0000000000400614 000614 000009 00  AX  0   0  4
  [16] .rodata           PROGBITS        0000000000400620 000620 000004 04  AM  0   0  4
  [17] .eh_frame_hdr     PROGBITS        0000000000400624 000624 000034 00   A  0   0  4
  [18] .eh_frame         PROGBITS        0000000000400658 000658 0000f4 00   A  0   0  8
  [19] .init_array       INIT_ARRAY      0000000000600e10 000e10 000008 00  WA  0   0  8
  [20] .fini_array       FINI_ARRAY      0000000000600e18 000e18 000008 00  WA  0   0  8
  [21] .jcr              PROGBITS        0000000000600e20 000e20 000008 00  WA  0   0  8
  [22] .dynamic          DYNAMIC         0000000000600e28 000e28 0001d0 10  WA  6   0  8
  [23] .got              PROGBITS        0000000000600ff8 000ff8 000008 08  WA  0   0  8
  [24] .got.plt          PROGBITS        0000000000601000 001000 000030 08  WA  0   0  8
  [25] .data             PROGBITS        0000000000601030 001030 00001f 00  WA  0   0  8
  [26] .bss              NOBITS          000000000060104f 00104f 000001 00  WA  0   0  1
  [27] .comment          PROGBITS        0000000000000000 00104f 000034 01  MS  0   0  1
  [28] .shstrtab         STRTAB          0000000000000000 001930 00010c 00      0   0  1
  [29] .symtab           SYMTAB          0000000000000000 001088 000678 18     30  47  8
  [30] .strtab           STRTAB          0000000000000000 001700 000230 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings), l (large)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)
root@ubuntu32:~# readelf -S testing
There are 31 section headers, starting at offset 0x1828:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        08048154 000154 000013 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            08048168 000168 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            08048188 000188 000024 00   A  0   0  4
  [ 4] .gnu.hash         GNU_HASH        080481ac 0001ac 000020 04   A  5   0  4
  [ 5] .dynsym           DYNSYM          080481cc 0001cc 000060 10   A  6   1  4
  [ 6] .dynstr           STRTAB          0804822c 00022c 000052 00   A  0   0  1
  [ 7] .gnu.version      VERSYM          0804827e 00027e 00000c 02   A  5   0  2
  [ 8] .gnu.version_r    VERNEED         0804828c 00028c 000020 00   A  6   1  4
  [ 9] .rel.dyn          REL             080482ac 0002ac 000008 08   A  5   0  4
  [10] .rel.plt          REL             080482b4 0002b4 000018 08  AI  5  24  4
  [11] .init             PROGBITS        080482cc 0002cc 000023 00  AX  0   0  4
  [12] .plt              PROGBITS        080482f0 0002f0 000040 04  AX  0   0 16
  [13] .plt.got          PROGBITS        08048330 000330 000008 00  AX  0   0  8
  [14] .text             PROGBITS        08048340 000340 0001a2 00  AX  0   0 16
  [15] .fini             PROGBITS        080484e4 0004e4 000014 00  AX  0   0  4
  [16] .rodata           PROGBITS        080484f8 0004f8 000008 00   A  0   0  4
  [17] .eh_frame_hdr     PROGBITS        08048500 000500 00002c 00   A  0   0  4
  [18] .eh_frame         PROGBITS        0804852c 00052c 0000c4 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      08049f08 000f08 000004 00  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      08049f0c 000f0c 000004 00  WA  0   0  4
  [21] .jcr              PROGBITS        08049f10 000f10 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         08049f14 000f14 0000e8 08  WA  6   0  4
  [23] .got              PROGBITS        08049ffc 000ffc 000004 04  WA  0   0  4
  [24] .got.plt          PROGBITS        0804a000 001000 000018 04  WA  0   0  4
  [25] .data             PROGBITS        0804a018 001018 000017 00  WA  0   0  4
  [26] .bss              NOBITS          0804a02f 00102f 000001 00  WA  0   0  1
  [27] .comment          PROGBITS        00000000 00102f 000034 01  MS  0   0  1
  [28] .shstrtab         STRTAB          00000000 00171b 00010a 00      0   0  1
  [29] .symtab           SYMTAB          00000000 001064 000470 10     30  47  4
  [30] .strtab           STRTAB          00000000 0014d4 000247 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

Pra facilitar a análise, crie uma tabela com os valores em hexadecimal, em decimal e os valores de 64 bits menos os valores de 32 bits. Se 64 bits tiver valores maiores, o resultado é positivo. Do contrário, negativo. Isso mostra que a quantidade bytes alocados é maior ou menor em cada caso.

Name	Type	Size 64 bits (hexa)	Size 32 bits (hexa)	64 bits (dec)	32 bits (dec)	Bigger?
NULL	0	0	0	0	0	0
.interp	PROGBITS	00001c	154	28	340	-312
.note.ABI-tag	NOTE	20	168	32	360	-328
.note.gnu.build-id	NOTE	24	188	36	392	-356
.gnu.hash	GNU_HASH	00001c	0001ac	28	428	-400
.dynsym	DYNSYM	78	0001cc	120	460	-340
.dynstr	STRTAB	45	00022c	69	556	-487
.gnu.version	VERSYM	00000a	00027e	10	638	-628
.gnu.version_r	VERNEED	20	00028c	32	652	-620
.rela.dyn	RELA	18	0002ac	24	684	-660
.rela.plt	RELA	48	0002b4	72	692	-620
.init	PROGBITS	00001a	0002cc	26	716	-690
.plt	PROGBITS	40	0002f0	64	752	-688
.plt.got	PROGBITS	8	330	8	816	-808
.text	PROGBITS	0001a2	340	418	832	-414
.fini	PROGBITS	9	4,00E+04	9	262144	-262135
.rodata	PROGBITS	4	0004f8	4	1272	-1268
.eh_frame_hdr	PROGBITS	34	500	52	1280	-1228
.eh_frame	PROGBITS	0000f4	00052c	244	1324	-1080
.init_array	INIT_ARRAY	8	000f08	8	3848	-3840
.fini_array	FINI_ARRAY	8	000f0c	8	3852	-3844
.jcr	PROGBITS	8	000f10	8	3856	-3848
.dynamic	DYNAMIC	0001d0	000f14	464	3860	-3396
.got	PROGBITS	8	000ffc	8	4092	-4084
.got.plt	PROGBITS	30	1000	48	4096	-4048
.data	PROGBITS	00001f	1018	31	4120	-4089
.bss	NOBITS	1	00102f	1	4143	-4142
.comment	PROGBITS	34	00102f	52	4143	-4091
.shstrtab	STRTAB	00010c	00171b	268	5915	-5647
.symtab	SYMTAB	678	1064	1656	4196	-2540
.strtab	STRTAB	230	0014d4	560	5332	-4772

Como pode ser visto na tabela, em todas as partes de cabeçalho ELF do executável, 64 bits é menor.  O resultado é um binário com mais espaço na memória por alguma diferença na GNU libc, que não necessariamente significa que seu programa está ocupando mais memória em 64 bits. Pelo contrário. O stack pra dados alocados em geral é o mesmo que em 32 bits, mas o binário criado é mais eficiente com menor uso.

Espero com isso ter mostrado que 32 bits em arquitetura de 64 não traz um benefício real em dados. O motivo de pessoas dizerem que ficou mais rápido?  Pode ser o resultado de algum outro problema de HD ou coisa do tipo, mas não pela arquitetura da CPU rodar melhor 32 bits em 64 bits, como alguns acham que é. No fim, essa discussão de melhor ou pior fica num campo de sensações. Então, é complicado argumentar contra alguém que acha que ficou mais rápido.

Minha sugestão final: se tem uma CPU que suporta 64 bits, use 64 bits.