Performance C x C++

230 views
Skip to first unread message

Thiago Adams

unread,
Mar 14, 2015, 3:01:49 PM3/14/15
to ccppb...@googlegroups.com
Acho que o padrão de performance de todas as linguagens é o C.
A melhor propaganda sempre é "quase tão rápido como o C"

 Só que o C está meio apagado ultimamente, parece que absorvido e escondido pelo próprio pessoal do C++ incluindo o padrão.

Em que o C ou C++ é mais rápido e/ou vice versa?
 
Uma exemplo em que o C++ é mais rapido, é fazer  um sort aonde se passa um lambda e este lambda desaparece ficando inline, comparando com um mesmo algoritmo em C que fosse usar uma chamada de funcao não inline.

Acho que um programador C, se estivesse preocupado com isso não usaria um ponteiro de funçao. Ja para defender o C++, a melhor propaganda é que neste caso não é preciso se preocupar com nada.

O que acham? Outros exemplos?

Gianni

unread,
Mar 14, 2015, 3:12:02 PM3/14/15
to ccppb...@googlegroups.com
Uma coisa que a gente sempre fala que deve ser a 1a coisa para se ter
performance é deixar o compilador fazer seutrabalho. Então pensando por esse
lado, quanto mais a gente conseguir espressar nossa ideia ao compillador, e
deixar ele se preocupar com performance, melhor. E, justamente, o C++ tem um
vocabulário melhor. Ao invés de falar 'função', podemos falar função, função
virtual, lambda, etc. Com C++, o compilador vai ter mais chance de otimizar.

Marcelo Zimbres

unread,
Mar 14, 2015, 6:15:34 PM3/14/15
to ccppb...@googlegroups.com
Ninguém conseguiu ainda fazer uma biblioteca para a transformada
rápida de Fourier (FFT) em C++ que seja tão rápida quanto a disponível
em c pela FFTW. Ví algumas pessoas na lista da Boost falando sobre ter
uma versão em C++, mas parecem discrentes de que um dia a eficiência
proporcionada pela metaprogramação possa algum dia alcançar o FFTW.
O FFTW por sua vez, apesar de ser código c, tem algumas de suas partes
geradas automaticamente por outro programa,
que se não me falha a memória é escrito em ocalm.

Em alguma cppcon recente, teve um cara falando também que se a
eficiência tiver que ser muito alta a metaprogramação tem que ser
abandonada. Em geral essa demanda aparece em processamento de
quantidades enormes de dados.

Dado que a FFT é um dos algoritimos mais importantes e mais usados no
mundo, seria uma vitória enorme pro c++ ter uma implementação que
pudesse brigar com a de outras linguagens.

Marcelo
> --
> Antes de enviar um e-mail para o grupo leia:
> http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
> --~--~---------~--~----~------------------------------
> [&] C & C++ Brasil - http://www.ccppbrasil.org/
> Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
> Para mais opções, visite http://groups.google.com/group/ccppbrasil
> --~--~---------~--~----~--~-~--~---~----~------------

André Tupinambá

unread,
Mar 14, 2015, 7:06:17 PM3/14/15
to C/C++ Brasil
Essa é uma discussão complicada.

Acredito que um desenvolvedor ninja em C e outro em C++ vão fazer códigos com desempenho similar em ambas nas linguagens. Ninja nessa definição é aquele que conhece tão bem o compilador e o processador que "sabe" qual o melhor jeito de usar a linguagem para chegar em um executável com melhor desempenho.

O que eu vejo de vantagem no C++ não está em conseguir "ser tão rápido quanto em C" para o desenvolvedor ninja, e sim ter um desempenho ótimo para o desenvolvedor acima da média "somente".

Por acaso é um pouco sobre o que vou falar no encontro, em ter uma biblioteca como a Boost::SIMD que pode dobrar o desempenho pelo uso da vetorização de vários processadores (x86-SSE, x86-AVX, ARM-Neon, etc.). Dá pra fazer na mão? Dá claro, mas dá muito mais trabalho, além de ser necessário criar um código diferente para cada processador. Daria para fazer algo similar com macros em C? Possivelmente. Mas em C++ é mais "limpo" e o compilador te ajuda mais a encontrar os erros (leia-se é tipado).

É como o paper sobre o Map Reduce do Google, ele não é a solução mais rápida para fazer a distribuição de um problema em um cluster. Mas por conseguir tornar isso muito mais simples para todos, o desempenho geral melhorou porque mais ferramentas estavam disponíveis.

[]'s
André Tupinambá

Marcelo Zimbres

unread,
Mar 14, 2015, 7:31:43 PM3/14/15
to ccppb...@googlegroups.com
Infelizmente não vou poder comparecer a esse encontro, Gostaria de ver
a apresentação do Boost.Simd. Tudo que eu encontrei no Boost.Simd foi
código para alinhamento, o mesmo que o Boost.Align. Eu gostaria de
saber a outra parte, o que o boost align não aferece e que está no
boost.simd. Queria saber se vão fazer um merge entre os dois.

Marcelo

André Tupinambá

unread,
Mar 14, 2015, 8:41:57 PM3/14/15
to C/C++ Brasil
Eu fiz um código de teste, está no github [1], todas as medições foram feitas com ele.
Não é um programa extenso, mas ele faz algo (é uma eliminação de gauss).
[]'s
André Tupinambá

Rodrigo Madera

unread,
Mar 16, 2015, 7:37:51 PM3/16/15
to ccppb...@googlegroups.com
2015-03-14 19:15 GMT-03:00 Marcelo Zimbres <mzim...@gmail.com>:
> em c pela FFTW. Ví algumas pessoas na lista da Boost falando sobre ter
> uma versão em C++, mas parecem discrentes de que um dia a eficiência
> proporcionada pela metaprogramação possa algum dia alcançar o FFTW.

Muito, mas muito estranho.

A metaprogramação teoricamente é tão eficiente quanto funções
estritamente feitas "na mão".

Tem link para essa discussão? Ainda mais vindo do povo da Boost.

Por outro lado, a concordo com Tupinambá sobre o debate principal de
Ninja vs Ninja. Com qualquer tecnologia é assim, e principalmente para
C e C++.

Metaprog não é mágica, é apenas a definição macabra de inlines tipadas
ao extremo, se quiser chamar de uma forma vulgar.

Isto sempre me lembra o exemplo que Strauss me deu certa vez, de um
projeto feito em Java onde o criador fez um milagre, no seu mais puro
significado, para fazer um dos servidores mais performáticos já vistos
na linguagem. Ele teve de tomar cuidados pra evitar a GC
completamente, e para otimizar a JVM específica para os acessos aos
dados.

Do mesmo jeito, os ponteiros do C afetam a performance em alguns
casos. Então o peão tem que saber o que está fazendo a nível bem
detalhado.

Idem para C++ e para C++ metaprogramado: o cara tem que saber o
resultado das expressões q

Madera

Rodrigo Madera

unread,
Mar 16, 2015, 7:39:33 PM3/16/15
to ccppb...@googlegroups.com
(email cortado)

... das expressões que são escritas.

Madera

Marcelo Zimbres

unread,
Mar 17, 2015, 9:33:18 AM3/17/15
to ccppb...@googlegroups.com
Madera,

Veja por exemplo a resposta de Pascal Germroth em
http://boost.2283326.n4.nabble.com/Request-to-contribute-boost-FFT-td4648003.html

Vejam por exemplo esse link: http://www.fftw.org/faq/section4.html#whyfast

Não vejo como uma implementação baseada em templates poderia ter essas
features (vejam a palavra runtime).

Sobre a apresentação na cppcon sobre o porque de abandonar expression
templates para eficiência, vou precisar procurar, se encontrar
retorno.

Marcelo


Em 16 de março de 2015 20:39, Rodrigo Madera
<rodrigo...@gmail.com> escreveu:

Thiago Adams

unread,
Mar 17, 2015, 10:16:14 AM3/17/15
to ccppb...@googlegroups.com
2015-03-16 20:37 GMT-03:00 Rodrigo Madera <rodrigo...@gmail.com>:
2015-03-14 19:15 GMT-03:00 Marcelo Zimbres <mzim...@gmail.com>:
> em c pela FFTW. Ví algumas pessoas na lista da Boost falando sobre ter
> uma versão em C++, mas parecem discrentes de que um dia a eficiência
> proporcionada pela metaprogramação possa algum dia alcançar o FFTW.

Muito, mas muito estranho.

A metaprogramação teoricamente é tão eficiente quanto funções
estritamente feitas "na mão".

Tem link para essa discussão? Ainda mais vindo do povo da Boost.

Por outro lado, a concordo com Tupinambá sobre o debate principal de
Ninja vs Ninja. Com qualquer tecnologia é assim, e principalmente para
C e C++.

Metaprog não é mágica, é apenas a definição macabra de inlines tipadas
ao extremo, se quiser chamar de uma forma vulgar.


Atualmente estou com dúvida sobre a performance do move, mas não tenho tempo para testar.
A dúvida é a seguinte, até para mostrar o dilema..

Se fizer um vector de strings , quando o vector é redimensionado ele move as strings uma a uma?
Não seria mais rápido um memmove do que fazer move disso?

Se isso for verdade, como corrigir?
Para contornar o vector teria que ter um traits que para string se dê conta que o melhor não é mover e sim copiar (memcopy)?
Mas se eu fizer o meu próprio tipo, como ele decide se é melhor copiar ou mover?

 

Marcelo Zimbres

unread,
Mar 17, 2015, 10:25:24 AM3/17/15
to ccppb...@googlegroups.com
Thiago,

não entendo sua conclusão. Mover n strings deve ser mais rápido do que
copiar as n strings para uma nova região de memória, não? Lembrando
que mover é praticamente trocar ponteiros i.e. O(1) e copiar O(n).

Marcelo

Felipe Magno de Almeida

unread,
Mar 17, 2015, 10:41:54 AM3/17/15
to CcppBrasil
2015-03-17 11:16 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
>
>
> 2015-03-16 20:37 GMT-03:00 Rodrigo Madera <rodrigo...@gmail.com>:
>>
>> 2015-03-14 19:15 GMT-03:00 Marcelo Zimbres <mzim...@gmail.com>:
>> > em c pela FFTW. Ví algumas pessoas na lista da Boost falando sobre ter
>> > uma versão em C++, mas parecem discrentes de que um dia a eficiência
>> > proporcionada pela metaprogramação possa algum dia alcançar o FFTW.
>>
>> Muito, mas muito estranho.
>>
>> A metaprogramação teoricamente é tão eficiente quanto funções
>> estritamente feitas "na mão".
>>
>> Tem link para essa discussão? Ainda mais vindo do povo da Boost.
>>
>> Por outro lado, a concordo com Tupinambá sobre o debate principal de
>> Ninja vs Ninja. Com qualquer tecnologia é assim, e principalmente para
>> C e C++.
>>
>> Metaprog não é mágica, é apenas a definição macabra de inlines tipadas
>> ao extremo, se quiser chamar de uma forma vulgar.
>>
>
> Atualmente estou com dúvida sobre a performance do move, mas não tenho tempo
> para testar.
> A dúvida é a seguinte, até para mostrar o dilema..
>
> Se fizer um vector de strings , quando o vector é redimensionado ele move as
> strings uma a uma?
> Não seria mais rápido um memmove do que fazer move disso?

Você não pode fazer memcpy (por que memmove? certamente os espaços
não fazem overlap) de não-PODs. Claro que, porém, a implementação STL
é livre para especializar o container em strings e otimizar da forma que
achar melhor. Mas não pode implementar essa otimização genericamente.

> Se isso for verdade, como corrigir?
> Para contornar o vector teria que ter um traits que para string se dê conta
> que o melhor não é mover e sim copiar (memcopy)?

Copiar strings não se usa memcpy, talvez somente para o caso de uma
string pequena com SBO.

> Mas se eu fizer o meu próprio tipo, como ele decide se é melhor copiar ou
> mover?

Ele vai sempre mover, se for possível, e você pode implementar o move
como bem entender. Veja que copiar é uma refinação do conceito de mover,
aonde:

Dado a,b = b':

a = move(b);
assert(a == b');

X a = b; (em cópia)
assert(a == b' && b == b');

Ou seja, move é uma cópia que não requer que o objeto anterior mantenha
o valor que possuia.

> --
> Antes de enviar um e-mail para o grupo leia:
> http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
> --~--~---------~--~----~------------------------------
> [&] C & C++ Brasil - http://www.ccppbrasil.org/
> Para sair dessa lista, envie um e-mail para
> ccppbrasil-...@googlegroups.com
> Para mais opções, visite http://groups.google.com/group/ccppbrasil
> --~--~---------~--~----~--~-~--~---~----~------------



--
Felipe Magno de Almeida

Rodrigo Madera

unread,
Mar 17, 2015, 11:15:53 AM3/17/15
to ccppb...@googlegroups.com
Zimbres,
Obrigado pelos links! Vou olhar pra entender melhor o problema.

Thiago,
Fiquei curioso com tua pergunta e pesquisei no google pelo benchmark
colaborativo disso. Achei logo no primeiro resultado uma pergunta
muito legal e cheia de detalhes sobre o ganho com move semantics [1].

Bora implementar isso em C puro usando apenas uma mão e uma perna
amarrada? rs ;-)

Madera

[1] http://stackoverflow.com/questions/14293151/missing-performance-improvements-with-c11-move-semantics

Thiago Adams

unread,
Mar 17, 2015, 12:31:02 PM3/17/15
to ccppb...@googlegroups.com
Vou explicar melhor a dúvida.. o meu palpite é que C seria mais rápido. 
(Depois posso fazer o teste)

Imagine um 

std::vector< std::string > v;

v.emplace_back("s1");
v.emplace_back("s2");
..
etc
Vai chegar um momento que vai ser preciso alocar mais memória. Certo?
Então o que vai acontecer? Imagino que as strings serão movidas uma a uma 
para a nova memória alocada, e provavelmente o destructor das strings movidas também serão chamados.

Agora, comparando com C, se as strings fossem armazenadas em uma  struct

struct string
{
  size_t size;
  char * text; 
}

Esta realocação poderia ser feita com um realloc, ou mesmo que fosse uma memoria nova seria apenas o caso de fazer um memcopy. Não é necessário fazer loop nenhum e nem ficar chamando um move individual do item e nem um destructor. 



Felipe Magno de Almeida

unread,
Mar 17, 2015, 12:35:03 PM3/17/15
to CcppBrasil
2015-03-17 13:30 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
> Vou explicar melhor a dúvida.. o meu palpite é que C seria mais rápido.
> (Depois posso fazer o teste)
>
> Imagine um
>
> std::vector< std::string > v;
>
> v.emplace_back("s1");
> v.emplace_back("s2");
> ..
> etc
> Vai chegar um momento que vai ser preciso alocar mais memória. Certo?
> Então o que vai acontecer? Imagino que as strings serão movidas uma a uma
> para a nova memória alocada, e provavelmente o destructor das strings
> movidas também serão chamados.
>
> Agora, comparando com C, se as strings fossem armazenadas em uma struct
>
> struct string
> {
> size_t size;
> char * text;
> }
>
> Esta realocação poderia ser feita com um realloc, ou mesmo que fosse uma
> memoria nova seria apenas o caso de fazer um memcopy. Não é necessário fazer
> loop nenhum e nem ficar chamando um move individual do item e nem um
> destructor.

Você está copiando uma string sem ownership do buffer. Você pode criar uma
string_ref (já tem proposta para essa em C++14) que seja standard-layout.
Assim terá a possibilidade de usar otimização do vector para standard-layout,
se sua STL tiver.

Porém, terá que manter o ownership da string externamente de alguma forma.

Thiago Adams

unread,
Mar 17, 2015, 12:50:24 PM3/17/15
to ccppb...@googlegroups.com
Quer dizer que preciso de um compilador C++ 14  e uma STL que suporte standard-layout para competir com uma solução em C de 40 anos atrás?


Felipe Magno de Almeida

unread,
Mar 17, 2015, 12:55:50 PM3/17/15
to CcppBrasil
2015-03-17 13:50 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
>
> 2015-03-17 13:34 GMT-03:00 Felipe Magno de Almeida
> <felipe.m...@gmail.com>:
>
>> 2015-03-17 13:30 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
>> > Vou explicar melhor a dúvida.. o meu palpite é que C seria mais rápido.
>> > (Depois posso fazer o teste)

[snip]

> Quer dizer que preciso de um compilador C++ 14 e uma STL que suporte
> standard-layout para competir com uma solução em C de 40 anos atrás?

Por que? Qualquer um pode escrever uma classe standard-layout de
string_ref. Standard-layout porém é um conceito de C++11, mas você
pode usar um POD em C++98. Só que não terá como incluir
construtores ao POD.

> --

[]'s,

Thiago Adams

unread,
Mar 17, 2015, 1:00:11 PM3/17/15
to ccppb...@googlegroups.com
Só para colocar mais lenha na fogueira :D ..

E dai quando eu precisar passar o item do vector para uma função que tem uma assinatura:

void f(const std::string& s)
{
}

vai ocorrer uma copia da string_ref para std::string?  Já que não são a mesma classe?

Felipe Magno de Almeida

unread,
Mar 17, 2015, 1:10:30 PM3/17/15
to CcppBrasil
Acho que a idéia do string_ref é ter uma função to_string ou algo assim.
Não deixar essa conversão implicita. Mas deixa a conversão contrária
implícita. De std::string para string_ref (sem cópia). Mas no caso, string_ref
é para ser usado como parâmetro, por isso a conversão implicita,
exatamente para subtituir std::string no seu exemplo, quando o mesmo
não precisa manter ownership para além da execução da função.

> Antes de enviar um e-mail para o grupo leia:
> http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
> --~--~---------~--~----~------------------------------
> [&] C & C++ Brasil - http://www.ccppbrasil.org/
> Para sair dessa lista, envie um e-mail para
> ccppbrasil-...@googlegroups.com
> Para mais opções, visite http://groups.google.com/group/ccppbrasil
> --~--~---------~--~----~--~-~--~---~----~------------



Thiago Adams

unread,
Mar 17, 2015, 1:14:22 PM3/17/15
to ccppb...@googlegroups.com
Até o presente momento não conheço nenhum caso de conversão sem cópia.

Ver o caso de ter um  allocator diferente entre duas std::string. 

Um allocator diferente cria outro tipo.

Acho que eles querem corrigir isso também, se não me falha memória.
Cada versão nova do C++ tem um fix para um problema criado na versão anterior.




--

Antes de enviar um e-mail para o grupo leia:
                     http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
--~--~---------~--~----~------------------------------
[&] C & C++ Brasil - http://www.ccppbrasil.org/
Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
Para mais opções, visite http://groups.google.com/group/ccppbrasil
--~--~---------~--~----~--~-~--~---~----~------------

Felipe Magno de Almeida

unread,
Mar 17, 2015, 1:17:06 PM3/17/15
to CcppBrasil
2015-03-17 14:14 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
> Até o presente momento não conheço nenhum caso de conversão sem cópia.

Por isso o nome string_ref, não é uma cópia. É uma classe
que representa
uma referência a uma string. Por isso a mesma não copia a string e nem libera
a memória no final. É exatamente equivalente ao POD em C que você usou
como exemplo.

> Ver o caso de ter um allocator diferente entre duas std::string.
>
> Um allocator diferente cria outro tipo.

No string_ref não existe allocator.
Felipe Magno de Almeida

Thiago Adams

unread,
Mar 17, 2015, 1:24:41 PM3/17/15
to ccppb...@googlegroups.com
2015-03-17 14:16 GMT-03:00 Felipe Magno de Almeida <felipe.m...@gmail.com>:
2015-03-17 14:14 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
> Até o presente momento não conheço nenhum caso de conversão sem cópia.

Por isso o nome string_ref, não é uma cópia. É uma classe
que representa
uma referência a uma string. Por isso a mesma não copia a string e nem libera
a memória no final. É exatamente equivalente ao POD em C que você usou
como exemplo.

> Ver o caso de ter um  allocator diferente entre duas std::string.
>
> Um allocator diferente cria outro tipo.

No string_ref não existe allocator.




Em todo caso, nesta situação, seria terrível ter outro lugar com ownership das strings, como você tinha dito no início.

"A string-like object that refers to a const sized piece of memory owned by another object."




Thiago Adams

unread,
Mar 17, 2015, 1:30:09 PM3/17/15
to ccppb...@googlegroups.com

Dúvida parecida se aplica no caso de 

std::vector < std:unique_ptr<A>  > 

Usei bastante vectors de unique_ptr pela praticidade. Mas estou meio desconfiado e não tive tempo de investigar.

- Mesma questão do move. Tem um swap para cada ponteiro?
- Será que se eu criar 3 vectors

std::vector < std:unique_ptr<A>  > 
std::vector < std:unique_ptr<B>  > 
std::vector < std:unique_ptr<C>  > 

ele cria um o código do unique_tr para A outro para B e outro para C? E mais o código do vector para std:unique_ptr<A>  std:unique_ptr<B>  e std:unique_ptr<C>  ? Isso não vai inchar meu exe? 



Felipe Magno de Almeida

unread,
Mar 17, 2015, 2:06:57 PM3/17/15
to CcppBrasil
2015-03-17 14:30 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
>
> Dúvida parecida se aplica no caso de
>
> std::vector < std:unique_ptr<A> >
>
> Usei bastante vectors de unique_ptr pela praticidade. Mas estou meio
> desconfiado e não tive tempo de investigar.
>
> - Mesma questão do move. Tem um swap para cada ponteiro?
> - Será que se eu criar 3 vectors
>
> std::vector < std:unique_ptr<A> >
> std::vector < std:unique_ptr<B> >
> std::vector < std:unique_ptr<C> >
>
> ele cria um o código do unique_tr para A outro para B e outro para C?

Sim.

> E mais
> o código do vector para std:unique_ptr<A> std:unique_ptr<B> e
> std:unique_ptr<C> ? Isso não vai inchar meu exe?

Depende. Você exporta todos os simbolos? No Linux isso é o
padrão. Se não, daí depende de quais funções você vai usar.
Para o código do unique_ptr o inline deveria tomar conta disto já que
a função é bem pequena e provavelmente seu tamanho total
é menor com inline do que sem ele. Já outras funções no vector
podem ser maiores, mas geralmente push_back é inlined (no
MSVC não era pois sua implementação era meio bloated,
não sei como está agora), mas outras funções que o push_back
pode chamar talvez não sejam inlined ou o inline poderia
deixar ainda mais inchado.

IOW, só medindo.

> www.thradams.com
>
> --

Regards,

Marcelo Zimbres

unread,
Mar 17, 2015, 3:26:28 PM3/17/15
to ccppb...@googlegroups.com
Thiago,

eu acho que você esta comparando macãs com laranjas. Os containers da stl ou a
classe std::string por exemplo, são de uso geral. Não adianta por exemplo você
implementar uma std::string em c e falar "a versão em c e mais rápida" sem
saber qual foi a estrutura de dados e os algoritmos por traz dela. Para ser
justo você teria que escolher um algoritmo especifico ou uma estrutura de dados
com uma árvore e implementa-lo tanto em C com em C++, usando todos os recursos
da linguagem disponíveis em cada uma. Ai sim vai dar pra ter uma ideia.

Eu não vejo nenhum motivo pra C++ ser mais lento, e vejo muitos pra ter um
código muito mais legível e seguro.

Em 17 de março de 2015 15:06, Felipe Magno de Almeida
<felipe.m...@gmail.com> escreveu:

Thiago Adams

unread,
Mar 17, 2015, 5:24:04 PM3/17/15
to ccppb...@googlegroups.com

Aqui vai um teste... usei VC++ 2015

Para strings pequenas o C++ ganha.. para strings maiores C ganha.

Depois vou fazer a otimização da string pequena em C para o C ganhar sempre :D

Considerando tudo que o C++ fez inline.. o compilador foi muito bom. 

Para o teste ser justo no que eu queria medir tentei copiar a logica do grow da STL da MS.
incrementa 50% da capacidade.

Ou seja, em outros compiladores o resultado pode ser diferente. Quando mais alocar de ante mão, mais rápido.


--------


#include <string>
#include <vector>
#include <string.h>

#include <time.h>

struct str
{
    char * text;
    size_t size;
};

struct strs
{
    struct str * strings;
    size_t size;
    size_t capacity;
};

#define STRS_INIT { 0, 0, 0}

inline int strs_reserve(struct strs* p, size_t nelements)
{
    int r = 1;
    if (nelements > p->capacity)
    {
        void* pnew = p->strings;
        pnew = realloc(pnew, nelements * sizeof(struct str));
        if (pnew)
        {
            p->strings = (struct str*)pnew;
            p->capacity = nelements;
            r = 0;
        }
    }
    return r;
}

inline int strs_grow(struct strs* p, size_t nelements)
{
    int r = 0;
    if (nelements > p->capacity)
    {
        size_t new_nelements = p->capacity + p->capacity / 2;
        if (new_nelements < nelements)
        {
            new_nelements = nelements;
        }
        //1 2 3 4 6 9 13 19 28 42 63...
        r = strs_reserve(p, new_nelements);
    }
    return r;
}

inline int strs_emplace_back(struct strs* p, const char* text)
{
    int r = strs_grow(p, p->size + 1);
    if (r == 0)
    {
        size_t text_size = strlen(text);
        char * pcopy = (char*)malloc(sizeof(char) * (text_size + 1));
        if (pcopy)
        {
            strcpy_s(pcopy, text_size + 1, text);
            struct str * pstr = &(p->strings[p->size]);
            pstr->text = pcopy;
            pstr->size = text_size;
            p->size++;
        }
        else
        {
            r = 1;
        }
    }
    return r;    
}

void strs_destroy(struct strs* p)
{
    for (int i = 0; i < p->size; i++)
    {
        free(p->strings[i].text);
    }
    free(p->strings);
}

void fc()
{
    for (int k = 0; k < 1000; k++)
    {
        struct strs v = STRS_INIT;

        char buffer[] = "stringmaiorainda_stringmaiorainda";

        for (int i = 0; i < 1000; i++)
        {
            strs_emplace_back(&v, buffer);
        }

        strs_destroy(&v);
    }
}

void f()
{
    for (int k = 0; k < 1000; k++)
    {
        std::vector<std::string> v;

        char buffer[] = "stringmaiorainda_stringmaiorainda";

        for (int i = 0; i < 1000; i++)
        {
            v.emplace_back(buffer);
        }
    }
}

void run_test(const char* message, void(*test)(void))
{
    time_t start = clock();
    test();
    printf("%s %d\n", message, int(clock() - start));
}

int main(int argc, char* argv[])
{
    run_test("c", &fc);
    run_test("c++", &f);    
    return 0;
}


P.

unread,
Mar 17, 2015, 6:22:00 PM3/17/15
to ccppb...@googlegroups.com
Alguns comentários.

* A omissão de verificação do retorno de strs_emplace_back em fc não pode ser permitida; acrescentar um teste para o valor de retorno mais a ação mais simples que consegui pensar -- abort() -- reduziu a distância entre os dois exemplos.

* realloc é uma grande vantagem da implementação strs porque é permitido para a implementação aumentar o comprimento do segmento "in place" de tal modo que seja desnecessário copiar o segmento; sts_reserve faz bom uso dessa característica. Porém, quando realloc precisa copiar o segmento, faz efetivamente um memcpy, o que é proibido para objetos C++ não "standard layout".

* O teste fc é mais rápido que o teste f mesmo com o acréscimo indicado na minha primeira observação no -- gcc version 5.0.0 20150310 (experimental) (GCC) -- com otimização -O2

* O uso da palavra-chave inline neste programa não tem qualquer efeito útil.


Abraços!
P.

Francisco Lopes

unread,
Mar 17, 2015, 11:01:06 PM3/17/15
to ccppb...@googlegroups.com
Só pra efeito de registro (pois bench biased de CvsC++, assunto mais velho que CvsJava, não é meu hobby), resultado
no clang:

Mesma coisa. Performance == GCC.

Algumas pequenas alterações como mudando o tamanho da variável buffer:

Em um caso com -Ofast, mesmo no OS X, o gcc 4.9 fez alguma coisa que deixou o c++/c == 2/3 tempo…
Outra alteração de dado, deixou no Clang c++/c == 1/5 tempo… (GCC  C/C++ manteve a mesma performance Clang C).

Volta e meia um não otimiza o que o outro otimiza, ultimamente me tem mais acontecido com o clang que com o gcc.

Alterações que fiz foram propositais dadas as aberturas para má performance que surgem na implementação C que teoricamente
uma implementação boa de C++ (ou boa de C), não sofreria, independente disto o bench é irrelevante, a discussão é uma
perda de tempo a não ser pra lição de sempre: profile quando for o caso, quando for aplicável.

IMO, esse bench é picuinha =) e se encaixa muito em otimização prematura sem muito fundamento, não que otimização
prematura seja algo definitivamente ruim…

[]s

Francisco Lopes

unread,
Mar 17, 2015, 11:02:39 PM3/17/15
to ccppb...@googlegroups.com
ah, não use funções _s … o jeito de programar pra Windows sem Windows é usar os padrões e código portável.

Vinícius dos Santos Oliveira

unread,
Mar 18, 2015, 12:03:29 AM3/18/15
to ccppbrasil
Em 17 de março de 2015 19:22, P. <pedro....@gmail.com> escreveu:
* realloc é uma grande vantagem da implementação strs porque é permitido para a implementação aumentar o comprimento do segmento "in place" de tal modo que seja desnecessário copiar o segmento; sts_reserve faz bom uso dessa característica. Porém, quando realloc precisa copiar o segmento, faz efetivamente um memcpy, o que é proibido para objetos C++ não "standard layout".

Esse link discute alguns dos problemas dos allocators (dos quais muitos foram corrigidos no padrão C++11): http://upcoder.com/6/custom-vector-allocation/

No texto, também há menções a realloc e allocators que façam uso do mesmo.

--
Vinícius dos Santos Oliveira

Marcelo Zimbres

unread,
Mar 18, 2015, 9:50:40 AM3/18/15
to ccppb...@googlegroups.com
" O uso da palavra-chave inline neste programa não tem qualquer efeito útil."

Nem mesmo para evitar erros de múltiplas definições?

Marcelo

Em 18 de março de 2015 01:03, Vinícius dos Santos Oliveira
<vini.i...@gmail.com> escreveu:

P.

unread,
Mar 18, 2015, 9:58:43 AM3/18/15
to ccppb...@googlegroups.com
Neste programa, que erros de múltipla definição aconteceriam?
Abraços!
P.

Marcelo Zimbres

unread,
Mar 18, 2015, 10:17:17 AM3/18/15
to ccppb...@googlegroups.com
"Neste programa, que erros de múltipla definição aconteceriam?"

Pra eu saber que não haveria nenhum, eu teria que ler o código
e verificar que cada função é usada apenas uma vez. Acho portanto
boa prática usar o inline nesse caso, mesmo que não tenha tenha efeito sobre
a performance. Alguns no entanto poderiam argumentar que não é boa prática.
De qualquer forma, entendo o que quiz dizer.

Marcelo

Уθя¡ςκ

unread,
Mar 18, 2015, 10:18:17 AM3/18/15
to ccppb...@googlegroups.com
Pra questão de diminuir a diferença visual no código: http://coliru.stacked-crooked.com/a/0f103d4e24a6d375

Outro ponto, aqui da pra testar varios compiladores: http://melpon.org/wandbox/.

Questão é que o default do experimental GCC (que eu lembre é -O2) não da diferença aí, provavelmente por ser experimental talvez,
enquanto que as versões anteriores há.

Claro que dá pra corrigir e divagar na injustiça da alteração, etc, etc. Mas no meio disto, é interessante ver o ponto
de C++ estar "infectado" com uma zero-terminated C string no código inicial "C++". Se o código fosse mais C++ utilizando C++
strings como feito na alteração, teria potencial pro benefício visto. O código C poderia seguir os mesmos benefĩcios alterando
a interface das funções, etc.
Para sair dessa lista, envie um e-mail para ccppbrasil-unsubscribe@googlegroups.com

Vinícius dos Santos Oliveira

unread,
Mar 18, 2015, 10:27:11 AM3/18/15
to ccppbrasil
Em 18 de março de 2015 11:17, Marcelo Zimbres <mzim...@gmail.com> escreveu:
Pra eu saber que não haveria nenhum, eu teria que ler o código
e verificar que cada função é usada apenas uma vez

Não, a função pode ser chamada/usada múltiplas vezes. O que não pode é ela ser definida múltiplas vezes.

Thiago Adams

unread,
Mar 18, 2015, 12:58:10 PM3/18/15
to ccppb...@googlegroups.com

Utilitário.. 
Como ver a taxa de crescimento do vector em determinada implementação?

    size_t cap = 0;
    std::vector<int> v;
    for (size_t i = 0; i < 1000; i++)
    {
        v.emplace_back(i);
        if (cap != v.capacity())
        {
            cap = v.capacity();
            std::cout << v.capacity() << " ";
        }
        
    }
    std::cout << std::endl;


P.

unread,
Mar 18, 2015, 1:32:30 PM3/18/15
to ccppb...@googlegroups.com


Em quarta-feira, 18 de março de 2015 11:17:17 UTC-3, mzimbres escreveu:
"Neste programa, que erros de múltipla definição aconteceriam?"

Pra eu saber que não haveria nenhum, eu teria que ler o código
e verificar que cada função é usada apenas uma vez.


O seu jeito de falar, no subjuntivo, parece que a nossa discussão trata de um programa desconhecido, hipotético.
Ora, bro, o programa do Thiago está todo no email e é composto por uma única unidade de tradução com poucas linhas de código.
Não diga "teria quer ler o código"; leia o código.
Abraços!
P.

Thiago Adams

unread,
Mar 18, 2015, 2:23:56 PM3/18/15
to ccppb...@googlegroups.com

A versão anterior tinha bastante diferença entre C e C++ para strings pequenas.

A ideia original era saber se mover cada item poderia prejudicar o C++, comparado com algo em C.
O teste acaba misturando outras coisas, mas por mim tudo bem, é uma forma de levantar outras questões também.

Fiz uma nova versão, que agora tem otimização de pequenas strings no C. 
O legal que dá para testar até online.
Em todos os meus testes o C tem um resultado melhor. Qualquer um pode testar em seus compiladores de preferência.

( Posso ter cometido algum erro já que não estou usando muitas as sttrings.. )


#include <string>
#include <vector>
#include <string.h>
#include <iostream>

#include <time.h>

#define LOOP_COUNT 1000

#define STRING_ITEM "small"
//#define STRING_ITEM "large large large large large"

#define LOCAL_BUFFER_SIZE sizeof(size_t) * 4

struct str
{
    char * text;
    size_t size;
    char localbuffer[LOCAL_BUFFER_SIZE];
    int ismall;
};

inline void str_destroy(struct str* p)
{
    if (!p->ismall)
    {
        free(p->text);
    }
}

inline int str_init(struct str* p, const char * text)
{
    int r = 1;
    size_t text_size = strlen(text) + 1;

    int ismall = text_size < LOCAL_BUFFER_SIZE;

    char * pcopy = ismall ?
        p->localbuffer : (char*)malloc(sizeof(char) * (text_size + 1));

    if (pcopy)
    {
        strncpy(pcopy, text, text_size);
        p->ismall = ismall;
        p->text = pcopy;
        p->size = text_size;
        r = 0;
    }

    return r;
}

struct strs
{
    struct str * strings;
    size_t size;
    size_t capacity;
};

#define STRS_INIT { 0, 0, 0 }

inline int strs_reserve(struct strs* p, size_t nelements)
{
    int r = 1;
    if (nelements > p->capacity)
    {
        struct str* pnew = p->strings;
        pnew = (struct str*)realloc(pnew, nelements * sizeof(struct str));
        
        if (p->strings != NULL)
        {            
            for (size_t i = 0; i < p->size; i++)
            {
                if (pnew[i].ismall)
                {
                    /*correção do ponteiro interno*/
                    pnew[i].text = pnew[i].localbuffer;
                }
            }
        }
        
        if (pnew)
        {
            p->strings = pnew;
            p->capacity = nelements;
            r = 0;
        }
    }
    return r;
}

inline int strs_grow(struct strs* p, size_t nelements)
{
    int r = 0;
    if (nelements > p->capacity)
    {
        size_t new_nelements = p->capacity + p->capacity / 2;
        if (new_nelements < nelements)
        {
            new_nelements = nelements;
        }
        r = strs_reserve(p, new_nelements);
    }
    return r;
}

inline int strs_emplace_back(struct strs* p, const char* text)
{
    int r = strs_grow(p, p->size + 1);
    if (r == 0)
    {
        r = str_init(&p->strings[p->size], text);
        if (r == 0)
        {            
            p->size++;
        }        
    }
    return r;
}

void strs_destroy(struct strs* p)
{
    for (size_t i = 0; i < p->size; i++)
    {
        str_destroy(&p->strings[i]);        
    }
    free(p->strings);
}

int fc()
{
    int r = 0;
    char buffer[] = STRING_ITEM;
    for (int k = 0; k < LOOP_COUNT; k++)
    {
        struct strs v = STRS_INIT;
        for (int i = 0; i < LOOP_COUNT; i++)
        {
            if (strs_emplace_back(&v, buffer) != 0)
            {
                r = 0;
                break;
            }
        }

        strs_destroy(&v);
    }
    return r;
}

int f()
{
    char buffer[] = STRING_ITEM;
    for (int k = 0; k < LOOP_COUNT; k++)
    {
        std::vector<std::string> v;

        for (int i = 0; i < LOOP_COUNT; i++)
        {
            v.emplace_back(buffer);
        }
    }
    return 0;
}

int run_test(const char* message, int(*test)(void))
{
    time_t start = clock();
    int r = test();
    printf("%s %d\n", message, int(clock() - start));
    return r;
}

int main(int argc, char* argv[])
{
    run_test("c", &fc);
    run_test("c++", &f);
    return 0;
}

-- 

O strcopy_s não tem nos outros compiladores? Não é padrao do C 11?




P.

unread,
Mar 18, 2015, 2:30:39 PM3/18/15
to ccppb...@googlegroups.com
Esta é a minha versão do original acrescido de verificações de erro em fc() que eu considero necessárias para uma comparação entre equivalentes. No meu programa os loops são todos dez vezes maiores; a execução continua rápida. Os números de três execuções em seguida estão no final. P.


[pedro.lamarao@localhost yggdrasil]$ cat thiago.cpp
            strncpy(pcopy, text, text_size + 1);

            struct str * pstr = &(p->strings[p->size]);
            pstr->text = pcopy;
            pstr->size = text_size;
            p->size++;
        }
        else
        {
            r = 1;
        }
    }
    return r;   
}

void strs_destroy(struct strs* p)
{
    for (size_t i = 0; i < p->size; i++)

    {
        free(p->strings[i].text);
    }
    free(p->strings);
}

char buffer[] = "stringmaiorainda_stringmaiorainda";

int fc ()
{
    int r = 0;
    for (int k = 0; k < 10000; k++)

    {
        struct strs v = STRS_INIT;

        for (int i = 0; i < 10000; i++)
        {
            r = strs_emplace_back(&v, buffer);
            if (r != 0) break;
        }

        strs_destroy(&v);
        if (r != 0) break;
    }
    return r;
}

int f()
{
    for (int k = 0; k < 10000; k++)

    {
        std::vector<std::string> v;

        for (int i = 0; i < 10000; i++)
        {
            v.emplace_back(buffer);
        }
    }
    return 0;
}

void run_test(const char* message, int(*test)(void))

{
    time_t start = clock();
    test();
    printf("%s %d\n", message, int(clock() - start));
}

int main(int argc, char* argv[])
{
    run_test("c", &fc);
    run_test("c++", &f);   
    return 0;
}
[pedro.lamarao@localhost yggdrasil]$ g++5 -v
Using built-in specs.
COLLECT_GCC=g++5
COLLECT_LTO_WRAPPER=/opt/gcc-5.0/libexec/gcc/x86_64-redhat-linux/5.0.0/lto-wrapper
Target: x86_64-redhat-linux
Configured with: ../gcc/configure --prefix=/opt/gcc-5.0 --enable-bootstrap --enable-shared --enable-threads=posix --enable-checking=release --enable-multilib --with-system-zlib --enable-__cxa_atexit --disable-libunwind-exceptions --enable-gnu-unique-object --enable-linker-build-id --with-linker-hash-style=gnu --enable-languages=c,c++,lto --enable-plugin --enable-initfini-array --disable-libgcj --enable-gnu-indirect-function --with-tune=generic --with-arch_32=i686 --build=x86_64-redhat-linux
Thread model: posix

gcc version 5.0.0 20150310 (experimental) (GCC)
[pedro.lamarao@localhost yggdrasil]$ g++5 -std=c++1y -O2 -Wall thiago.cpp
[pedro.lamarao@localhost yggdrasil]$ LD_LIBRARY_PATH=/opt/gcc-5.0/lib64:$LD_LIBRARY_PATH ./a.out
c 8419831
c++ 12093387
[pedro.lamarao@localhost yggdrasil]$ LD_LIBRARY_PATH=/opt/gcc-5.0/lib64:$LD_LIBRARY_PATH ./a.out
c 10031405
c++ 12519761
[pedro.lamarao@localhost yggdrasil]$ LD_LIBRARY_PATH=/opt/gcc-5.0/lib64:$LD_LIBRARY_PATH ./a.out
c 9851459
c++ 12916206

P.

unread,
Mar 18, 2015, 2:36:05 PM3/18/15
to ccppb...@googlegroups.com
Em quarta-feira, 18 de março de 2015 15:23:56 UTC-3, Thiago Adams escreveu:
 
O strcopy_s não tem nos outros compiladores? Não é padrao do C 11?



strcopy_s é Microsoft.
Como a Microsoft é forte, existe um "technical report" no ISO.
Todos ignoram.
A alternativa é strncpy.
Abraços!
P.

Thiago Adams

unread,
Mar 18, 2015, 2:39:22 PM3/18/15
to ccppb...@googlegroups.com
Geralmente eu olho aqui:


e ali consta 
strcpy_s

Pena que eles não colocam quem implementa..


--
Antes de enviar um e-mail para o grupo leia:
http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
--~--~---------~--~----~------------------------------
[&] C & C++ Brasil - http://www.ccppbrasil.org/
Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
Para mais opções, visite http://groups.google.com/group/ccppbrasil
--~--~---------~--~----~--~-~--~---~----~------------

P.

unread,
Mar 18, 2015, 2:42:50 PM3/18/15
to ccppb...@googlegroups.com
Este é um manual de referência do compilador C.
Segundo o que está ali, realmente, incorporaram no C11.
Porém, estamos usando o compilador C++...
Talvez essas coisas sejam incorporadas depois.
Os dois grupos de trabalho estão evitando divergir.
Abraços!
P.

Thiago Adams

unread,
Mar 18, 2015, 2:47:34 PM3/18/15
to ccppb...@googlegroups.com
Na versão C++ não aparece

que beleza hein?

Uma curiosidade.. usando o compilador MS 2015, se eu usar o compile as C, o código fica mais lento comparando com compilar como C++. 



Francisco Lopes

unread,
Mar 18, 2015, 2:49:58 PM3/18/15
to ccppb...@googlegroups.com
E... não que a M$ esteja sendo super legal e está ativamente implementando C11. As funções
_s existem no compilador C++ M$ a eras, e inclusive podem estar totalmente incompativeis com
o que veio a aparecer no C11 (e que não existe em nenhum padrão C++).

Desculpe se coloquei um olhar na performance como um todo e não apenas em manter lento só pra
testar os moves. Acontece que estes strcpy e strlen gratuitos no meio do bolo pra medir performance
não me agrada muito.

Att.


P.

unread,
Mar 18, 2015, 3:47:53 PM3/18/15
to ccppb...@googlegroups.com
Thiago, em strs_reserve, não é necessário checar se p->strings é NULL já que é invariante que se p->strings é NULL então p->size é 0.
Ao invés disso, verifique se p->strings é diferente de pnew: caso sejam iguais é desnecessário completar o "move".
Isso significará que se realloc estende o segmento "in place" então strs_reserve é O(1).
Abraços!
P.


Em quarta-feira, 18 de março de 2015 15:23:56 UTC-3, Thiago Adams escreveu:
 

Thiago Adams

unread,
Mar 18, 2015, 4:17:23 PM3/18/15
to ccppb...@googlegroups.com
Entendi ..
Me parece que o codigo poderia ser assim: tem que lembrar que pnew pode falhar e vai ser null
if (pnew && p->strings && pnew != p->strings)
{
..
}
ou 
if (pnew && pnew != p->strings)
e depois size vai zero caso seja a 1 vez

Mas incluir este pnew != p->strings é interessante mesmo não precisa arrumar nada.


--
Antes de enviar um e-mail para o grupo leia:
http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
--~--~---------~--~----~------------------------------
[&] C & C++ Brasil - http://www.ccppbrasil.org/
Para sair dessa lista, envie um e-mail para ccppbrasil-...@googlegroups.com
Para mais opções, visite http://groups.google.com/group/ccppbrasil
--~--~---------~--~----~--~-~--~---~----~------------

P.

unread,
Mar 18, 2015, 4:26:33 PM3/18/15
to ccppb...@googlegroups.com
Uma versão pessoal do seu segundo programa.
O texto está modificado para exibir uma maneira mais C++ de escrever programas como esse.
Incluí uma função reserve diferente que usa malloc/free para demonstrar o desempenho caso não seja permitido usar realloc com os objetos do vetor.
Acho que a técnica de "short string optimization" do g++ 5 é melhor porque a diferença de tempos neste caso é quase nenhuma no meu ambiente, o que não faz sentido.
http://pastebin.com/0vGhvCDV
Abraços!
P.

Thiago Adams

unread,
Mar 18, 2015, 4:54:39 PM3/18/15
to ccppb...@googlegroups.com

Em código C eu evito ter múltiplos returns para evitar ter que limpar algo no meio do caminho.
É interessante esta comparação sem realloc.

Este sizeof(str) é interessante em C pois parece templates :D
A referencia (&) nestes casos é mais uma documentacao...(nao nulo)
Percebi que codigo c , geralmente o pessoal assume ponteiros de entrada nao nulo by default e nem assert colocam (git).

Thiago Adams

unread,
Mar 18, 2015, 5:02:34 PM3/18/15
to ccppb...@googlegroups.com

Tinha um bug na versão que coloquei aqui no size da string.

Aqui tem uma versão atualizada
http://www.thradams.com/codeblog/strlist.htm


  1. int str_ctor (str & self, char const * text)
  2. {
  3.     self.size = strlen(text);
  4.     self.ismall = self.size < LOCAL_BUFFER_SIZE;


Tem que somar 1 para o \0
  1.     self.ismall = ( self.size + 1 )< LOCAL_BUFFER_SIZE;




inline int str_init(struct str* p, const char * text)
{
    int
 r = 1;
    size_t text_size = strlen(text);

    int ismall = (text_size + 1) < LOCAL_BUFFER_SIZE;

    
char * pcopy = ismall ?
        p->localbuffer : (char*)malloc(sizeof(char) * (text_size + 1));

    if
 (pcopy)
    {
        strncpy(pcopy, text, text_size + 1);
        p->ismall = ismall;
        p->text = pcopy;
        p->size = text_size;
        r = 0;
    }

    return r;
}


2015-03-18 17:26 GMT-03:00 P. <pedro....@gmail.com>:

adb

unread,
Mar 19, 2015, 5:25:23 PM3/19/15
to ccppb...@googlegroups.com

Concordo com mzimbres;
acrescento que não dá para querer que C++ seja tão rápido quanto C o tempo todo, 
pois o mesmo adiciona vários níveis de abstração, segurança e simplicidade.

Em última instância não se deve esquecer que C++ é um superconjunto de C;
posso usar ponteiros e estruturas normalmente (e de forma mais segura com new/delete);
e se precisar de mais desempenho, posso incluir trechos de códigos em assembler...

Particularmente acho C++ muito mais simples e direto;
acrescento que as novidades de C++11/14 - para mim - tem sido muito úteis;

Neste momento estamos começando a trabalhar com processamento paralelo usando
múltiplas-threads (muito simples!) e CUDA;

Alguns exemplo de CUDA 7 - que já inclui recursos de C++11, 
mostram bem as vantagens do uso de C++11/14,
os códigos ficam muito mais simples e diretos.

André

P.

unread,
Mar 19, 2015, 5:51:35 PM3/19/15
to ccppb...@googlegroups.com
Em quinta-feira, 19 de março de 2015 18:25:23 UTC-3, adb escreveu:
 
Concordo com mzimbres;
acrescento que não dá para querer que C++ seja tão rápido quanto C o tempo todo, 
pois o mesmo adiciona vários níveis de abstração, segurança e simplicidade.

 
É uma diretriz de projeto explícita do grupo de trabalho do C++ que este não introduza desperdício frente a um programa equivalente em C.
Devemos entender que os envolvidos estão fadados ao eterno fracasso?
Abraços!
P.

Rodrigo Madera

unread,
Mar 19, 2015, 6:19:08 PM3/19/15
to ccppb...@googlegroups.com
Eu ia fazer uma pergunta similar.

Dizer que " não dá para querer que C++ seja tão rápido quanto C o
tempo todo" é um atestado de ign.. de preconceito tecnológico sem
fundamento.

adb,
Gostaria muito de entender o que te leva a pensar isso. Se pudesse
expor, ficaria mais fácil te entender.

Madera

Marcelo Zimbres

unread,
Mar 19, 2015, 6:33:12 PM3/19/15
to ccppb...@googlegroups.com
"Ora, bro, o programa do Thiago está todo no email e é composto por
uma única unidade de tradução com poucas linhas de código."

Você se refere a esse trecho específico de código e eu me referia ao
uso geral da palavra inline.

"Não diga "teria quer ler o código"; leia o código."

Novamente, pra esse caso específico eu posso ler, mas como me refiro
ao uso geral não vejo o problema do inline. Por isso o uso do
subjuntivo.

"É uma diretriz de projeto explícita do grupo de trabalho do C++ que
este não introduza desperdício frente a um programa equivalente em C."

Concordo. Não sou expert em linguagens, mas pelo que eu sei o C++ e o
C oferecem ao compilador as mesmas oportunidades em termos de
otimização. O fortram por exemplo, por não permitir aliasing de
ponteiros, era considerado mais rápido que C. Não sei como Fortram Vs.
C está hoje em dia, dado que C oferece o "restrict"
para evitar aliasing.

"Devemos entender que os envolvidos estão fadados ao eterno fracasso?"

Não entendi essa frase, quem são os envolvidos?

Marcelo

Marcelo Zimbres

unread,
Mar 19, 2015, 6:37:04 PM3/19/15
to ccppb...@googlegroups.com
"Dizer que " não dá para querer que C++ seja tão rápido quanto C o
tempo todo" é um atestado de ign.. de preconceito tecnológico sem
fundamento."

Nossa que palavras fortes ... Bem eu suponho que o colega se refira a
overheads que introduzimos para tornar o código mais seguro ex: usar
smart pointer em vez de ponteiros pelados, fazer cópias e mais cópias
quando usar a STL etc.

Marcelo

Rodrigo Madera

unread,
Mar 19, 2015, 6:39:48 PM3/19/15
to ccppb...@googlegroups.com
Marcelo,

Acabei de ler a thread e gerou as seguintes dúvidas:

2015-03-14 19:15 GMT-03:00 Marcelo Zimbres <mzim...@gmail.com>:
> Ninguém conseguiu ainda fazer uma biblioteca para a transformada
> rápida de Fourier (FFT) em C++ que seja tão rápida quanto a disponível
> em c pela FFTW. Ví algumas pessoas na lista da Boost falando sobre ter
> uma versão em C++, mas parecem discrentes de que um dia a eficiência
> proporcionada pela metaprogramação possa algum dia alcançar o FFTW.
> O FFTW por sua vez, apesar de ser código c, tem algumas de suas partes
> geradas automaticamente por outro programa,
> que se não me falha a memória é escrito em ocalm.

Olhando a thread, eu consegui ver o trecho de Pascal na thread [1] que
você enviou:

"""
Generally, I think it would be better if a boost::fft library would
primarily be a wrapper around existing FFT libraries, with the C++
implementation only used as a fallback for multiprecision or licensing
issues since it's unlikely a template implementation would catch up with
the years of optimization work that went into single- and
double-precision libraries like MKL and FFTW, especially FFTW's kernel
generator and planner.
But a reasonably fast (and open) multidimensional multiprecision FFT
implementation doesn't seem to exist yet.
"""

Dito isso, me parece que ele está comentando que **DEVIDO AO TRABALHO
EXISTENTE** para otimizar FFTW, não seria provável que metaprogramando
seria tão eficiente quanto este que tem tantas horas trabalhadas.

Além disso, vendo o teu segundo link em "Question 4.2. Why is FFTW so
fast?" [2], temos o comentário que o programa (a lib) é rápida em
parte por gerar código para alguns casos. Exatamente o que o template
metaprogramming faz, conceitualmente.

Dito isso, devo dizer que na minha digestão das informações, eles não
disseram que Template Metaprogramming não chegaria à velocidade de
qualquer implementação em C, mas sim de umas em específico que tem
anos de melhorias e análise. Por exemplo, bibliotecas feitas pela
Intel para usar as instruções dos processadores com o máximo de
eficiência.

Ora, isso não é C vs. C++... isso é Assembly Ninja vs QualquerOutro. E
ele pode ser usado em C quanto C++.

Teoricamente, ao meu ver, se uma biblioteca TMP nascesse para FFT,
teria a oportunidade de ser otimizada com o tempo e chegar a um nível
competitivo, sempre e quando tivesse as otimizações comparáveis às que
o FFTW teve para certas situações. Pra isso precisaríamos dos
engenheiros dos processadores fazerem cheat nas implementações e socar
uns __asm da vida.

Continuo com o pensamento claro que TMP gera código ultra otimizado
por gerar ele em tempo de compilação. Uma ajuda extra do programador
pode ajudar em alguns casos. Mas não há empecilho para dizer que o C
supere.

O que acham?
Madera

[1] http://boost.2283326.n4.nabble.com/Request-to-contribute-boost-FFT-td4648003.html
[2] http://www.fftw.org/faq/section4.html#whyfast

Rodrigo Madera

unread,
Mar 19, 2015, 6:46:21 PM3/19/15
to ccppb...@googlegroups.com
2015-03-19 19:37 GMT-03:00 Marcelo Zimbres <mzim...@gmail.com>:
> Nossa que palavras fortes ... Bem eu suponho que o colega se refira a
> overheads que introduzimos para tornar o código mais seguro ex: usar
> smart pointer em vez de ponteiros pelados, fazer cópias e mais cópias
> quando usar a STL etc.

É que falar sem fundamento é fácil, Marcelo. Não é teu caso, mas existe.
Quando o debate atinge folclore deixa de ser ciência.

Sobre teus comentários: sim. Temos bibliotecas e mecanismos que podem
estragar a performance do C++ se não tiver cuidado, mas isso seria o
estilo do programa. Tem gente que não usa STL. E tem compiladores que
eliminam cópias com extrema facilidade. Tem gente em C que abusa de
ponteiros, de bibliotecas, e isso também estraga.

A questão é C++ em si e seus mecanismos. Dizer "C é mais rápido" sem
pensar é fácil. Tem que comparar as mais profundas características das
linguagens para fazer afirmações.

Resumindo, acho que Linus, mais uma vez, resumiu:

"Talk is cheap. Show me the code."

Ou no nosso caso aqui na lista:

"Talk is cheap. Show me the benchmarks."

Madera

Rafael Silva

unread,
Mar 19, 2015, 7:08:24 PM3/19/15
to ccppb...@googlegroups.com
Olá a todos, como fiquei sem internet (ainda estou) só vi essa discussão agora, esse tipo de assunto me interessa e eu tive a felicidade de tropeçar no livro do Jason Gregory "Game Engine Architecture".

Lembrando da palestra no CPPCon do engenheiro da M$ sobre compiladores, não foi chover no molhado, ótimas respostas foram dadas, mas acho válido ressaltar o Gianni e o que eu aprendi lendo até agora o livro do Jason.

NÃO OTIMIZE O QUE VOCÊ NÃO SABE. O compilador já faz otimizações e na maioria dos casos os programadores mais atrapalham do que ajudam, PORQUE não conhecem a fundo a arquitetura do hardware que programam, eu EMPIRICAMENTE percebi que a maioria dos problemas de performance não são do algoritmo (Cormen O(1), O(log(n), etc...), mas sim de PIPELINE desperdiçado por maus hábitos de programação que foram criados por "boas intenções".

As coisas que eu aprendi estudando engine de games são muito F***, eu nunca imaginei que coisas inocentes que eu fazia ou mesmo um NEW que devolve endereço desalinhado poderiam pesar tanto na performance de um programa.

No mais o compilador do C e do C++ é o mesmo não? Parece que a sua pergunta arremete a "Qual linguagem o compilador otimiza melhor em binário?". Eu prefiro focar em saber se o meu código roda 100% ou se está dando cache miss, pipeline vazio, algoritmo tosco O(n^2).

Desculpem responder sem ler todas as respostas, estou sem condição no momento, mas quis colocar minha opinião.

Att,
Rafael Soares.

Francisco Lopes

unread,
Mar 20, 2015, 7:07:18 AM3/20/15
to ccppb...@googlegroups.com
Pra quem perdeu, este assunto que rolou uns dias atrás no reddit encaixa na conversa,
e é interessante para as implementações da stdlib. Aparentemente um defeito inserido
com as novidades na linguagem a ser corrido numa versão futura do padrão.


On Mar 14, 2015, at 16:01, Thiago Adams <thiago...@gmail.com> wrote:

Acho que o padrão de performance de todas as linguagens é o C.
A melhor propaganda sempre é "quase tão rápido como o C"

 Só que o C está meio apagado ultimamente, parece que absorvido e escondido pelo próprio pessoal do C++ incluindo o padrão.

Em que o C ou C++ é mais rápido e/ou vice versa?
 
Uma exemplo em que o C++ é mais rapido, é fazer  um sort aonde se passa um lambda e este lambda desaparece ficando inline, comparando com um mesmo algoritmo em C que fosse usar uma chamada de funcao não inline.

Acho que um programador C, se estivesse preocupado com isso não usaria um ponteiro de funçao. Ja para defender o C++, a melhor propaganda é que neste caso não é preciso se preocupar com nada.

O que acham? Outros exemplos?

Thiago Adams

unread,
Mar 20, 2015, 7:19:57 AM3/20/15
to ccppb...@googlegroups.com

2015-03-19 19:46 GMT-03:00 Rodrigo Madera <rodrigo...@gmail.com>:
...
"Talk is cheap. Show me the code."

Ou no nosso caso aqui na lista:

"Talk is cheap. Show me the benchmarks."
....


Foi o caso aqui. Coloquei uma dúvida e um exemplo concreto e completo.
 

Thiago Adams

unread,
Mar 20, 2015, 7:27:44 AM3/20/15
to ccppb...@googlegroups.com


Mais uma dúvida:

A a = ...;
A b;

b = std::move(a);

Em muitos casos após um move não se deseja mais fazer nada. Mesmo assim o compilador vai chamar o destructor.
Então no caso do unique_ptr movido, aquele cara que foi movido vai ter o destructor chamado e vai ter um if para verificar se precisa fazer algo. Isso é um overhead (bem pequeno) do C++ em relação ao C. Em C, se eu entrego um ponteiro não é preciso  fazer mais nada.
Acho que não fale a pena entrar em muitos detalhes disto e discutir mas falei apenas para levantar a questão. Este overhead apesar de pequeno, não poderia ser usado em uma lib padrão por exemplo que deveria ter a melhor performance já que não se sabe os requisitos de quem usa. 




Francisco Lopes

unread,
Mar 20, 2015, 7:32:38 AM3/20/15
to ccppb...@googlegroups.com
Correção: não sei se chegaram a conclusão se é um defeito ou se é possível otimizar sob as novas regras e não está sendo feito.
A discussão mudou da época que participei.

Att.

Francisco Lopes

unread,
Mar 20, 2015, 7:44:06 AM3/20/15
to ccppb...@googlegroups.com
On Mar 20, 2015, at 8:27, Thiago Adams <thiago...@gmail.com> wrote:



Mais uma dúvida:

A a = ...;
A b;

b = std::move(a);

Em muitos casos após um move não se deseja mais fazer nada. Mesmo assim o compilador vai chamar o destructor.
Então no caso do unique_ptr movido, aquele cara que foi movido vai ter o destructor chamado e vai ter um if para verificar se precisa fazer algo. Isso é um overhead (bem pequeno) do C++ em relação ao C. Em C, se eu entrego um ponteiro não é preciso  fazer mais nada.

move é uma ferramenta que vc pode usar ou não, é comum passagem de ponteiro no mesmo estilo do C no C++, ninguém tem que ficar
preso ao que o C++ oferece de novo, encaixa na hora que faz sentido. Passagem de ponteiro C style não tem ownership, C não tem
destrutores, e portanto incapaz de expressar o que o C++ vai estar expressando ao utilizar move, é possível simular apenas.

Nota que este contexto aplicado a tipos do C, dá nisto:

"A trivial destructor is a destructor that performs no action. Objects with trivial destructors don't require a delete-expression and may be disposed of by simply deallocating their storage. All data types compatible with the C language (POD types) are trivially destructible."


Acho que não fale a pena entrar em muitos detalhes disto e discutir mas falei apenas para levantar a questão. Este overhead apesar de pequeno, não poderia ser usado em uma lib padrão por exemplo que deveria ter a melhor performance já que não se sabe os requisitos de quem usa. 





Gabriel Bassani Ribeiro

unread,
Mar 20, 2015, 7:47:04 AM3/20/15
to ccppb...@googlegroups.com
Essas discussões são mais antigas que nós, passa por nós e continua depois de nós. Loop eterno...

Francisco Lopes

unread,
Mar 20, 2015, 7:52:18 AM3/20/15
to ccppb...@googlegroups.com
Mas, é possível imaginar uma passagem de unique ownership na qual haja uma única destruição (quando o objeto final morre)
em uma linguagem que já tem sido construída com este design em mente (ao contrário do c++).
Não sei se o Rust faz isto, talvez sim.

José

unread,
Mar 20, 2015, 2:51:46 PM3/20/15
to ccppb...@googlegroups.com

Implementation      Lines per second
python (default)           3,571,428
cin (default/naive)          819,672
cin (no sync)             12,500,000
fgets                     14,285,714

P.

unread,
Mar 20, 2015, 3:06:46 PM3/20/15
to ccppb...@googlegroups.com
Em sexta-feira, 20 de março de 2015 08:27:44 UTC-3, Thiago Adams escreveu:
 
Acho que não fale a pena entrar em muitos detalhes disto e discutir mas falei apenas para levantar a questão. Este overhead apesar de pequeno, não poderia ser usado em uma lib padrão por exemplo que deveria ter a melhor performance já que não se sabe os requisitos de quem usa.


Que programa é esse para o qual um if a mais é mau negócio?
Que função de biblioteca é essa que estará no inner loop criando e destruindo unique_ptrs?
Assim saímos da engenharia, onde estávamos, e entramos na metafísica de software.
Abraços!
P.

Thiago Adams

unread,
Mar 20, 2015, 3:48:12 PM3/20/15
to ccppb...@googlegroups.com
Vamos pensar em um caso prático.

Imagina um http server super requisitado. 
Na versão do C++ 11, foi usado um shared_ptr < HttpConnection > para passar a conexão para um algoritmo assíncrono através de um lambda.

shared_ptr< HttpConnection > spconn;

RunAsync ( [spconn]] {
   ...
});

Como não era possível passar por move para um lambda o shared_ptr foi usado na época. (Lembrando que a contagem de referência não é barata)

(
 Contei esta historinha só para mostrar como o C++ sempre precisa de uma fix na versão seguinte. 
)


Agora com C++ 14, é possível usar unique_ptr  e jogar a conexão para dentro do lambda.

unique_ptr< HttpConnection > spconn;

RunAsync ( [sq = spconn]] {
   ...
});

Chamar o destrutor de spcon após ter sido movido é perda de tempo. O proprio "move" de unique_ptr para o lambda pode ser perda de tempo comparado com C, pois no C, a origem não precisa ser zerada (deixa o lixo lá e era isso), Neste caso é uma atribuição a mais e um if a mais por chamada. Ah e depois um if a mais lá no destructor do unique_ptr que foi movido. 
Sera que vai ter um fix no compilador no C++ 20 ? 

Parece loucura tal grau de otimização?

Se quiserem ter uma ideia de um http server com atenção a todo detalhe de performance e em C
recomendo:


Quando eu olhei a primeira vez eu pensei "são loucos". 
Mas o "colégio" deles é diferente.

Isso não é otimização prematura na minha opinião, e sim consciência do que ocorre e um estilo minimalista que evitar fazer o que não é preciso.
 
Para entrar no mundo real, teríamos que fazer testes com unique_ptr em loop e com moves. Será que não é melhor gastar tempo com C e não se preocupar o que as abstrações fazem ou deixam de fazer?


P.

unread,
Mar 20, 2015, 4:09:37 PM3/20/15
to ccppb...@googlegroups.com
Abaixo.



Em sexta-feira, 20 de março de 2015 16:48:12 UTC-3, Thiago Adams escreveu:
 
Agora com C++ 14, é possível usar unique_ptr  e jogar a conexão para dentro do lambda.

unique_ptr< HttpConnection > spconn;

RunAsync ( [sq = spconn]] {
   ...
});

Chamar o destrutor de spcon após ter sido movido é perda de tempo.



Então não chame:

  RunAsync( [sq = std::make_unique<HttpConnection>(args...) { ... }

Esses "ifs inúteis" do C++ costumam se justificar no hipotético código enxuto estilo C quando este código enxuto começa a incluir a verificação de todas as condições de erros de tempo de execução -- como esgotamento de memória, falha na alocação de sockets etc. -- e se torna então necessário incluir todos aqueles if (p == NULL) free(p) que nós vemos após um label no final da função.
unique_ptr existe porque é preciso desalocar com certeza em face da possibilidade de uma exceção.
Se você está escrevendo um programa que nunca vai levantar exceções, certamente usar unique_ptr é um desperdício.
Abraços!
P.

Thiago Adams

unread,
Mar 20, 2015, 4:28:13 PM3/20/15
to ccppb...@googlegroups.com
Não entendi, o ponto. Claro que no C++ o RAII está no núcleo e o melhor é usar unique_tr.
Mas podemos discutir a clareza do código RAII x limpeza manual e é outro topico.



P.

unread,
Mar 20, 2015, 4:43:26 PM3/20/15
to ccppb...@googlegroups.com
Não mencionei clareza de código.
Mencionei todos os /* if (error == 0) goto cleanup */ que estão omitidos dos típicos exemplos de "C é mais econômico" como os dados anteriormente, que revelam a lista com todos os supostos ifs inúteis do argumento inicial.
Os ifs que são supostamente inúteis não são inúteis e refletem todos aqueles ifs que existem no mundo real para verificar as condições de erro em tempo de execução que exigem a limpeza sistemática dos recursos alocados até a etapa presente do procedimento.
Esses exemplos artificiais não são bons pra julgar qual if é útil ou inútil.
unique_ptr é um desperdício apenas em programas onde erros jamais acontecerão e portanto existe certeza de que um recurso alocado na etapa anterior não pode vazar no decorrer das etapas sendo avaliadas.
O seu exemplo é um exemplo onde o programa aloca uma tarefa assíncrona para o qual ele movimenta um pacote de argumentos úteis; milagrosamente, a alocação da tarefa assíncrona não falha e portanto não é necessário verificar se o ponteiro foi ou não foi "movimentado" com sucesso.
Um programa em C do mundo real teria paranóicas verificações de falha e listas de free(p) e p = NULL .
Abraços!
P.

Thiago Adams

unread,
Mar 20, 2015, 5:16:50 PM3/20/15
to ccppb...@googlegroups.com


2015-03-20 17:43 GMT-03:00 P. <pedro....@gmail.com>:
Não mencionei clareza de código.
Mencionei todos os /* if (error == 0) goto cleanup */ que estão omitidos dos típicos exemplos de "C é mais econômico" como os dados anteriormente, que revelam a lista com todos os supostos ifs inúteis do argumento inicial.
Os ifs que são supostamente inúteis não são inúteis e refletem todos aqueles ifs que existem no mundo real para verificar as condições de erro em tempo de execução que exigem a limpeza sistemática dos recursos alocados até a etapa presente do procedimento.
Esses exemplos artificiais não são bons pra julgar qual if é útil ou inútil.
unique_ptr é um desperdício apenas em programas onde erros jamais acontecerão e portanto existe certeza de que um recurso alocado na etapa anterior não pode vazar no decorrer das etapas sendo avaliadas.
O seu exemplo é um exemplo onde o programa aloca uma tarefa assíncrona para o qual ele movimenta um pacote de argumentos úteis; milagrosamente, a alocação da tarefa assíncrona não falha e portanto não é necessário verificar se o ponteiro foi ou não foi "movimentado" com sucesso.
Um programa em C do mundo real teria paranóicas verificações de falha e listas de free(p) e p = NULL .
Abraços!
P.

Você está com preconceito por ter visto código C mal feito. Sem um exemplo nos entramos novamente em suposições. Se quiser usar o código da lista de string http://www.thradams.com/codeblog/strlist.htm para apontar problemas do C pode usar.


Rodrigo Madera

unread,
Mar 20, 2015, 5:27:05 PM3/20/15
to ccppb...@googlegroups.com
Thiago,

O kernel do Linux me parece uma obra referência em C. E acompanho ela religiosamente. 

O que me diz, dentro de apenas o escopo de limpeza, do código no final das funções, abaixo de um label ("cleanup" ou "exit", como Pedro disse)? Eles existem. São horríveis, e somente existem no Linux pois seu criador pensa como você em termos da superioridade da linguagem C, mas precisa desses artefatos de poluição que você cita não precisar. 

E sobre o unique_ptr para servidores, me parece uma simplificação demasiada. Raramente um servidor do mundo real poderá ser unique_ptr. Normalmente precisa ser shared em designs assíncronos miltithread pois sempre haverá uma ou outra task pendente pra usar o objeto. Todas em paralelo. Sem contagem de referência limitamos o paralelismo gravemente. 

Madera

Thiago Adams

unread,
Mar 20, 2015, 5:40:10 PM3/20/15
to ccppb...@googlegroups.com
 
>Eles existem. São horríveis, e somente existem no Linux pois seu criador pensa como você em >termos da superioridade da linguagem C, mas precisa desses artefatos de poluição que você cita >não precisar. 

Eu posso  defender o C++ também e falar das coisas boas. Mas acho que aqui na lista tem muito mais gente para defender o C++ do que C.
De modo geral, o que eu defendo em qualquer uma C ou C++ é manter os programas leves, rápidos e simples de manter e entender.  Anteriormente tinha dito que não tinha experiencia nenhuma com C. Agora já tenho um pouco, pois fiz umas ferramentas de uso interno na empresa que trabalho.

 

Rodrigo Madera

unread,
Mar 20, 2015, 6:09:05 PM3/20/15
to ccppb...@googlegroups.com
2015-03-20 18:40 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
> Eu posso defender o C++ também e falar das coisas boas. Mas acho que aqui
> na lista tem muito mais gente para defender o C++ do que C.
> De modo geral, o que eu defendo em qualquer uma C ou C++ é manter os
> programas leves, rápidos e simples de manter e entender. Anteriormente
> tinha dito que não tinha experiencia nenhuma com C. Agora já tenho um pouco,
> pois fiz umas ferramentas de uso interno na empresa que trabalho.

Entendo. Mas ainda preciso entender o porquê de você não precisar dos
"ifs desnecessários". O Linux precisa, e é em C. O C++ precisa. Só
você não? Tem algúm método de limpar recursos que não conheço? Estou
muito curioso!

Concordo que programas "simples" são melhores. Em coisas simples
raramente bugs sobrevivem.

Quando você ganhar mais experiência em C, talvez você repare que não
existe o milagre de eliminação de limpeza, de condicionais, e de além.

Seja usando RAII direto, seja de forma brilhante como Alexandrescu e
seu Scope Guard, ou seja em Czão mesmo, precisa desses ifs. Senão,
alguma merda vai acontecer no teu programa.

Madera

Thiago Adams

unread,
Mar 21, 2015, 2:07:47 PM3/21/15
to ccppb...@googlegroups.com
2015-03-20 19:09 GMT-03:00 Rodrigo Madera <rodrigo...@gmail.com>:
2015-03-20 18:40 GMT-03:00 Thiago Adams <thiago...@gmail.com>:
.........

Entendo. Mas ainda preciso entender o porquê de você não precisar dos
"ifs desnecessários". O Linux precisa, e é em C. O C++ precisa. Só
você não? Tem algúm método de limpar recursos que não conheço? Estou
muito curioso!



 Clarificando a questão do if desnecessário no C++.

A duvida é a seguinte:

Em C

struct A* a = malloc(sizeof(A));
struct A *b = a;
...
destroy(b);

eu movi a para b, e nunca mais vou usar "a" nem tocar nele. 

Comparando com unique_ptr e move do C++

 std::unique_ptr<A> a = std::make_unique<A>();
 std::unique_ptr<A> b(std::move(a));
..

Quando a for movido para b ele vai zerar a?
Quando "a" sair do escopo vai chamar o destrutor de a e verificar se o ponteiro não é nulo?
Se ele fizer estas duas coisas ele estará gerando mais operações do que as necessárias em C.

Este é o if desnecessário. 
Não consigo ver no disassembly se ele (VC++) faz consegue ou não sumir com este destructor.

------------------

A questão de como viver com RAII manual do C ao inves do automatico do C++, é bem interessante. Não existe milagre, para sumir com ifs de controle de erro. 
Já que é manual, ele pode ser usado em favor próprio, como por exemplo se pode ignorar um "destructor" como foi o caso do exemplo acima.
Os idiomas do C são diferentes, e se tenta minimar os ifs e returns no meio do fluxo.

Tenho um exemplo que é o seguinte:

Imagina que abro um arquivo e preciso fechar.

int read(const char* fileName)
{
  int r  = 1;
   FILE * f = fopen(fileName, "");
   if (f)
   {
     .....
     //varias linhas com return no meio
      .......
      close(f);
   }
  return r;
}

Ao invés de encher de código dentro do if e afastar a linha do close da linha do fopen, eu costumo criar outra função que não é mais responsável pelo recurso.

int read_file(FILE* f)
{
   ........
}

int read(const char* fileName)
{
  int r  = 1;
   FILE * f = fopen(fileName, "");
   if (f)
   {
      r = read_file(f);
      close(f);
   }
  return r;
}

Notem que agora a read terminou.É uma função pequena.
E lá  na read_file ninguém precisa se preocupar com o recurso file.

Felipe Magno de Almeida

unread,
Mar 21, 2015, 3:04:36 PM3/21/15
to CcppBrasil
Move não tem destructive-semantics em C++. Logo, o objeto continua existindo
e precisa ser destruido. Se você criar somente objetos, em C++, com destrutores
triviais. Assim como é obrigado em C, você terá exatamente a mesma performance
que C e poderá escolher não chamar os destrutures destes objetos e os que o
compilador chamar não farão diferença em performance.

> --
> Antes de enviar um e-mail para o grupo leia:
> http://www.ccppbrasil.org/wiki/Lista:AntesdePerguntar
> --~--~---------~--~----~------------------------------
> [&] C & C++ Brasil - http://www.ccppbrasil.org/
> Para sair dessa lista, envie um e-mail para
> ccppbrasil-...@googlegroups.com
> Para mais opções, visite http://groups.google.com/group/ccppbrasil
> --~--~---------~--~----~--~-~--~---~----~------------



--
Felipe Magno de Almeida

Thiago R Adams

unread,
Mar 21, 2015, 3:21:07 PM3/21/15
to ccppb...@googlegroups.com

O caso do unique_ptr não tem destrutor vazio.
Dai surge a duvida se eh bom usar ou não.todos nos vivíamos sem ele..agora vamos mudar de estilo?ex vector uniqueptr

From: Felipe Magno de Almeida
Sent: ‎21/‎03/‎2015 16:04
To: CcppBrasil
Subject: Re: [ccppbrasil] Performance C x C++

Francisco Lopes

unread,
Mar 21, 2015, 5:14:21 PM3/21/15
to ccppb...@googlegroups.com

Francisco Lopes

unread,
Mar 21, 2015, 5:21:33 PM3/21/15
to ccppb...@googlegroups.com
Foi fácil enganar o otimizador do gcc de forma ingenua, nem tanto o clang:

Francisco Lopes

unread,
Mar 21, 2015, 6:00:12 PM3/21/15
to ccppb...@googlegroups.com
Para o caso de estar envolvendo destruição simples, e não só free:


Nota que, não considero o assembly gerado como nenhuma verdade absoluta
que vá se aplicar à toda situação envolvendo qualquer destrutor/objeto. É apenas
o que acontece pra esse caso específico e irrelevante para a “vida real”.

Francisco Lopes

unread,
Mar 21, 2015, 6:02:41 PM3/21/15
to ccppb...@googlegroups.com
E, por via das dúvidas o opcode do clang é quase AI: http://goo.gl/sgHvRs. Preciso.

Thiago Adams

unread,
Mar 22, 2015, 11:37:24 AM3/22/15
to ccppb...@googlegroups.com
Legal..
Os compiladores C++ sumiram com tudo.

Tentando atacar se o move é problema e pegando de tudo um pouco em um teste genérico fiz outro exemplo:

C++ sem unique_ptr x C++ com unique_ptr. 

Ou seja, poderia ser uma pergunta "posso trocar meu estilo C++ 03 para um estilo C+ 11 com unique_ptr sem medo?"


#include <vector>
#include <memory>
#include <time.h>

const size_t TOTAL = 1000;
#define FRONT
//#define BACK
//#define ALL

struct shape
{
    virtual void draw() = 0;
    virtual ~shape() {}
};

struct box : public shape
{
    int w;  int h;
    virtual void draw() override
    {
    }
};

void f03()
{
    for (size_t k = 0; k < TOTAL; k++)
    {
        std::vector<shape*> shapes;
        for (int i = 0; i < TOTAL; i++)
        {
            shapes.emplace_back(new box());
        }

#ifdef ALL
        for (size_t i = 0; i < shapes.size(); i++)
        {
            delete shapes[i];
        }
        shapes.clear();
#else
        while (!shapes.empty())
        {
#ifdef FRONT
            delete shapes.front();
            shapes.erase(shapes.begin());
#endif
#ifdef BACK
            delete shapes.back();
            shapes.erase(shapes.end() - 1);
#endif
        }
#endif    
    }
}

void f11()
{
    for (size_t k = 0; k < TOTAL; k++)
    {
        std::vector<std::unique_ptr<shape>> shapes;
        for (int i = 0; i < TOTAL; i++)
        {
            shapes.emplace_back(std::make_unique<box>());
        }

#ifdef ALL
        shapes.clear();
#else

        while (!shapes.empty())
        {
#ifdef FRONT
            shapes.erase(shapes.begin());
#endif
#ifdef BACK
            shapes.erase(shapes.end() - 1);
#endif
        }
#endif

    }
}


void run_test(const char* message, void(*test)(void))
{
    time_t start = clock();
    test();
    printf("%s %d\n", message, (int)clock() - start);

}

int main(int argc, char* argv[])
{

#ifdef ALL
    printf("all\n\n");
#endif
#ifdef FRONT
    printf("front\n\n");
#endif
#ifdef BACK
    printf("back\n\n");
#endif

    run_test("C++ 03 :", &f03);
    run_test("C++ 11:", &f11);

    run_test("C++ 03 :", &f03);
    run_test("C++ 11:", &f11);

    return 0;
}

O resultado (VC++ 2015) foi que somente no caso de remover no começo (e provavelmente no meio mas não testei) a performance do unique_ptr foi pior (bem pior 2x). Lembrando que o unique_ptr tem um deleter tb. 

Ou seja, a mudança, a atualização para "use unique_ptr"  parece ser tranquila e sem prejuízo a não ser que alguém fique removendo coisas do vector que não me parece ser o caso comum.

 Depois vou fazer a versão C.

--

Agora indo além no assunto otimização, se os compiladores trabalharem tão bem haveria muitas outras coisas que poderiam ser simplificadas na linguagem.
Por exemplo, no C++ é possível criar objeto no heap ou stack. Se este conceito fosse unificado na linguagem e os compiladores fossem inteligentes eles poderiam decidir se usariam o objeto no stack ou heap assim teria uma forma unica de criar um objeto. 

Thiago Adams

unread,
Mar 22, 2015, 11:47:54 AM3/22/15
to ccppb...@googlegroups.com
Testei em um GCC online 4.8 e unique_ptr ficou pior em todos os casos.

Francisco Lopes

unread,
Mar 22, 2015, 2:12:32 PM3/22/15
to ccppb...@googlegroups.com
Não pra mim, no caso mais prático e usual (removi todos os ifdefs): http://coliru.stacked-crooked.com/a/dc264fae468f01e8.

On Mar 22, 2015, at 12:47, Thiago Adams <thiago...@gmail.com> wrote:

Testei em um GCC online 4.8 e unique_ptr ficou pior em todos os casos.


Francisco Lopes

unread,
Mar 22, 2015, 2:23:29 PM3/22/15
to ccppb...@googlegroups.com
Ao que parece (eu não constei na documentação, só testando no coliru mesmo), o -O do GCC default
é -O0, por isto saiu lento pra vc se vc testou em -O0, algo útil só pra debugging.

Francisco Lopes

unread,
Mar 22, 2015, 2:25:04 PM3/22/15
to ccppb...@googlegroups.com
coliru da pra setar flags do jeito que quiser, wandbox tem mais opcoes de compilador, mas
cada usa um setup fixo de flags ao que parece.

Francisco Lopes

unread,
Mar 22, 2015, 3:46:01 PM3/22/15
to ccppb...@googlegroups.com

On Mar 22, 2015, at 12:37, Thiago Adams <thiago...@gmail.com> wrote:

Agora indo além no assunto otimização, se os compiladores trabalharem tão bem haveria muitas outras coisas que poderiam ser simplificadas na linguagem.
Por exemplo, no C++ é possível criar objeto no heap ou stack. Se este conceito fosse unificado na linguagem e os compiladores fossem inteligentes eles poderiam decidir se usariam o objeto no stack ou heap assim teria uma forma unica de criar um objeto. 

Escape-analysis, veja em Go por exemplo: https://scvalex.net/posts/29/. As otimizações C e C++ que somem com mallocs/new
beiram a isto, mas acredito que seja bom que C e C++ não dependa disto somente, com o fim de que se tenha o controle nas
mãos de forma explicita quando for o caso.

JuciÊ Andrade

unread,
Mar 24, 2015, 7:02:50 PM3/24/15
to ccppb...@googlegroups.com
Quando o Assembly era mais usado para aplicações comerciais, os programadores determinavam exatamente onde residiam as variáveis do programa. Com o passar do tempo vieram os Super Ultra Macro Assemblers que libertavam os programadores de muitos detalhes mundanos como esse.

Houve um tempo, lá nos primórdios do C, em que o programador usava a palavra "register" pra dizer quais eram as variáveis que deveriam ocupar registradores. Depois de alguns anos os compiladores se tornaram capazes de tomar a decisão por nós.

Escape analysis é um outro passo no mesmo sentido. Mais cedo ou mais tarde nós não vamos mais decidir que variáveis ficam na pilha, ou na heap, mesmo porquê essa decisão está ficando cada vez mais difícil.

Os gurus do hardware já chegaram à conclusão que para tirar proveito de um número cada vez maior de processadores disponíveis é necessário ter uma hierarquia na distribuição da memória.

A bem da verdade, hoje já existe uma divisão. Simplificando, você tem um cachezinho pequeno, perto ali do core do processador, depois um outro compartilhado entre dois cores, talvez um terceiro nível, compartilhado entre todos os cores do mesmo chip, depois outro cache já na placa, antes da memória principal e, por fim, a própria memória principal (para limitar nossa discussão apenas à RAM). 

Hoje nós temos pouco controle sobre o que é guardado onde. O próprio processador se encarrega de mover a informação entre as camadas, à medida que vai precisando. Infelizmente, sem uma visão geral da aplicação, essa inteligência do hardware tem um alcance limitado.

Alguns estudos indicam [1] que uma solução mais eficaz seria o compilador indicar em qual camada dessa hierarquia de memória deve residir uma determinada informação. "Ah, então essa variável passa dessa thread para aquela outra? Ótimo, vou colocar o dado na memória compartilhada por dois cores, porque ninguém mais precisa enchergar essa informação e eu não ocupo o bus principal."

Pra isso tudo funcionar direito, é necessária uma orquestração entre o compilador, o runtime, o sistema operacional e o hardware.

Nesse mundo não vai adiantar o programador decidir se o dado fica na pilha ou na heap, entende? A questão não será tão simples. Mais uma vez teremos que abrir mão do controle e deixar por conta do compilador e de seus amiguinhos tomarem a decisão final, considerando todas as informações de que dispõem para nos ajudar.

Ou seja: você não vai mandar em mais nada, meu caro. Aceita, que dói menos.

Um grande abraço.

O JuciÊ

[1] ah, quer saber maiores informações? O Google é todo seu. Divirta-se.

JuciÊ Andrade

unread,
Mar 24, 2015, 7:45:19 PM3/24/15
to ccppb...@googlegroups.com
Perdoem a (falta de) ortografia.

Thiago Adams

unread,
Mar 25, 2015, 7:52:19 AM3/25/15
to ccppb...@googlegroups.com

Só para não deixar passar.. Fiz o teste em C. 
Os resultados foram bem parecidos com C++03 e C++11 usando o compilador VC 2015.
C ficou um pouquinho mais rápido nos testes. O shape não tem relação com C, mas não era o foco neste teste.

Conclusão.
No caso do VC 2015. se você não ficar deletando itens no meio do vector o unique_ptr não vai ferrar a performance. 
O C não apronta surpresas e sempre tem performance.

-----------


#include <stdlib.h>

#include <vector>
#include <memory>
#include <time.h>

const size_t TOTAL = 1000;
//#define FRONT
//#define BACK
void destroy_shape(void* p)
{
    shape * ps = (shape*)p;
    delete ps;
}

struct vptrs
{
    void ** data;
    size_t size;
    size_t capacity;
};

#define INIT_VPTRS { 0,0,0 }


inline int vptrs_reserve(struct vptrs* p, size_t nelements)
{
    int r = 0;
    if (nelements > p->capacity)
    {
        void** pnew = p->data;
        pnew = (void**)realloc(pnew, nelements * sizeof(void*));
        if (pnew)
        {
            p->data = pnew;
            p->capacity = nelements;
        }
        else
        {
            /*sem memoria*/
            r = 1;
        }
    }
    return r;
}

int vptrs_grow(struct vptrs* p, size_t nelements)
{
    int r = 0;
    if (nelements > p->capacity)
    {
        size_t new_nelements = p->capacity + p->capacity / 2;
        if (new_nelements < nelements)
        {
            new_nelements = nelements;
        }
        r = vptrs_reserve(p, new_nelements);
    }
    return r;
}

inline int vptrs_emplace_back(struct vptrs* p, void* pdata)
{
    int r = vptrs_grow(p, p->size + 1);
    if (r == 0)
    {
        p->data[p->size] = pdata;
        p->size++;
    }
    return r;
}

void vptrs_destroy(struct vptrs* p,
    void(*destroy)(void*))
{
    for (size_t i = 0; i < p->size; i++)
    {
        destroy(p->data[i]);
    }
    free(p->data);
}

inline void* vptrs_remove_back(struct vptrs* p)
{    
    void* pdata = p->data[p->size - 1];
    p->size--;
    return pdata;
}

inline void* vptrs_remove_front(struct vptrs* p)
{
    void* pdata = p->data[0];
    
    memmove(p->data,
            p->data + 1,
            sizeof(void*) * (p->size - 1));

    p->size--;

    return pdata;
}


void fC()
{
    for (size_t k = 0; k < TOTAL; k++)
    {
        struct vptrs shapes = INIT_VPTRS;
        for (int i = 0; i < TOTAL; i++)
        {
            vptrs_emplace_back(&shapes, (void*)new box());
        }

#ifdef ALL
        vptrs_destroy(&shapes, &destroy_shape);
#else
        while (shapes.size > 0)
        {
#ifdef FRONT
            destroy_shape(vptrs_remove_front(&shapes));
#endif
#ifdef BACK
            destroy_shape(vptrs_remove_back(&shapes));
#endif
        }
#endif    
    }
}

void run_test(const char* message, void(*test)(void))
{
    time_t start = clock();
    test();
    printf("%s %d\n", message, (int)clock() - start);

}


int main(int argc, char* argv[])
{

#ifdef ALL
    printf("all\n\n");
#endif
#ifdef FRONT
    printf("front\n\n");
#endif
#ifdef BACK
    printf("back\n\n");
#endif

    //A ordem pode alterar os resultados

    run_test("C     :", &fC);
    run_test("C++ 03 :", &f03);
    run_test("C++ 11:", &f11);
    

    printf("\n");
    

    run_test("C     :", &fC);

P.

unread,
Mar 25, 2015, 8:48:35 AM3/25/15
to ccppb...@googlegroups.com
O propósito deste teste é verificar o overhead de unique_ptr.
Você ainda está usando realloc, que é uma óbvia vantagem da sua implementação de vetor.
Este teste não é conclusivo sobre o overhead de unique_ptr.
Aposto dez reais que um profiler indicará a diferença dominante estando entre realloc versus new[]/delete[].
Abraços!
P.

Thiago Adams

unread,
Mar 25, 2015, 8:59:53 AM3/25/15
to ccppb...@googlegroups.com
2015-03-25 9:48 GMT-03:00 P. <pedro....@gmail.com>:
O propósito deste teste é verificar o overhead de unique_ptr.
Você ainda está usando realloc, que é uma óbvia vantagem da sua implementação de vetor.
Este teste não é conclusivo sobre o overhead de unique_ptr.
Aposto dez reais que um profiler indicará a diferença dominante estando entre realloc versus new[]/delete[].
Abraços!
P.


O teste era também em relação ao move, que o unique_ptr vai usar teoricamente.
Fora a questão do C, o teste pode ajudar a responder "posso trocar meu código C++ 03 para um estilo C++ 11 com unique_ptr sem medo?"
A diferença do C neste caso foi tão pouca que não vale a pena se preocupar com realloc x new.
E pensando de forma geral, o que interessa não é o detalhe da implementação e sim que estilo vou usar para criar uma lista polimórfica.  


JuciÊ Andrade

unread,
Jun 27, 2015, 4:34:23 AM6/27/15
to ccppb...@googlegroups.com
Só pra completar, já que tropecei no assunto novamente agora há pouco.
 
Quem tiver interesse em se aprofundar, procure por "Non-uniform memory access NUMA".
Reply all
Reply to author
Forward
0 new messages