C versus C++: Dá pra confiar na otimização do C++?

Não me entendam errado: C++ é muito bom para a maioria dos desenvolvimentos… mas não criar rotinas de alta-performance! Para demonstrar isso ainda mais estou conduzindo alguns testes simples e medindo ciclos gastos. Ao fazer isso topei com uma pequena surpresa. Dêem uma olhada nos dois códigos abaixo. O primeiro, em C++:

/* new.cc */
#include <iostream>
#include "cycle-counting.h"

using namespace std;

int main(void)
{
  long long t;
  char *p[1000]; // array de 1000 ponteiros.

  // Faz 1000 alocações de 1000 bytes.
  START_CYCLE_COUNT(t);
  for (int i = 0; i < 1000; i++)
    p[i] = new char [1000];
  STOP_CYCLE_COUNT(t);

  cout << (t / 1000) << " ciclos de máquina.\n";

  return 0;
}

E, agora, o código equivalente em C:

/* malloc.c */
#include <stdio.h>
#include <malloc.h>
#include "cycle-counting.h"

int main(void)
{
  long long t;
  char *p[1000];
  int i;

  START_CYCLE_COUNT(t);
  for (i = 0; i < 1000; i++)
    p[i] = malloc(1000);
  STOP_CYCLE_COUNT(t);

  printf("%lld ciclos de máquina.\n", t / 1000);
}

Eis a surpresa: Ao compilar com a máxima otimização do compilador, usando a opção -O3, eis o que obtenho…

$ gcc -O3 -mtune=native -o malloc malloc.c
$ g++ -O3 -mtune=native -o new new.cc
$ ./new
1280 ciclos de máquina.
$ ./malloc
0 ciclos de máquina.

Hã?! Zero ciclos? Zero? Como assim zero?

O que aconteceu é que o compilador jogou todo o loop do código em C no lixo porque percebeu que nenhum processamento real estava sendo feito. Ele viu que tinha código “morto” e o matou de fato! Ou seja, o gcc otimizou o código!

Já o operador new não pode ser simplesmente tratado como uma função comum (por que é isso que ele é, no fim das contas. Uma função!). Ele é uma função especial, um operator, que pode ter sido sobrecarregada (overloaded). O compilador só não tem como saber dessa sobrecarga de antemão. Isso significa que seus códigos em C++ não sofrem a máxima otimização que o compilador C pode fazer, justamente porque não há como prever algumas “mudanças” que ocorrerão em runtime.

Mas vamos a uma comparação mais útil, ok? Dessa vez compilarei os dois códigos sem nenhuma otimização:

$ gcc -O0 -o malloc malloc.c
$ g++ -O0 -o new new.cc
$ ./new
1280 ciclos de máquina.
$ ./malloc
1000 ciclos de máquina.

Os valores ai em cima são uma média de 1000 execuções de cada um dos códigos não otimizados. Deu pra perceber que malloc é cerca de 22% mais rápida que o operador new, fazendo o mesmo tipo de alocação? E olha que estamos alocando apenas arrays para o tipo primitivo char aqui. O operador new fica bem lento quando lidamos com arrays de classes que possuem construtores default (já que não é possível, usando o operador new padrão, alocar classes que precisem ser inicializadas com algum parâmetro!). Imagine a perda com a alocação de uma classe derivada!

A função free e o operador delete possuem comportamentos semelhantes, com a performance de delete sendo um pouco pior… No caso de free, tudo o que a libc faz é livrar-se de um nó de uma lista encadeada. Já delete tem que se preocupar, também, com destrutores.

Outro motivo da diferença de performance é o modelo de memória usado em ambas as linguagens. Não é à toa que C++ chama o espaço disponível para alocação de Free Store ao invés de Heap. A forma com que as estruturas de alocação são mantidas são diferentes para ambas as linguagens e C++ tende a ser um pouquinho menos performático que C, neste sentido.

Anúncios

5 comentários sobre “C versus C++: Dá pra confiar na otimização do C++?

      1. É isso ai, Raphael. Mas, acho que o cooler51 estava falando da má prática de não emparelhar um free ao um malloc (ou um delete a um new).

  1. Aparentemente as novas versões do C++ estão começando a abandonar o new em detrimento dos ponteiros inteligentes como unique_ptr e shared_ptr, só não sei dizer se é mais eficiente, já tem um tempo que não mexo em C++.

Deixe um comentário

Faça o login usando um destes métodos para comentar:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s