Pergunta

Tenho usado o rendimento em muitos dos meus programas Python e realmente esclarece o código em muitos casos.EU blogou sobre isso e é uma das páginas populares do meu site.

C# também oferece rendimento – é implementado por meio de manutenção de estado no lado do chamador, feito por meio de uma classe gerada automaticamente que mantém o estado, variáveis ​​locais da função, etc.

Atualmente estou lendo sobre C++0x e suas adições;e ao ler sobre a implementação de lambdas em C++ 0x, descobri que isso também foi feito por meio de classes geradas automaticamente, equipadas com operador() armazenando o código lambda.A pergunta natural se formou em minha mente:eles fizeram isso para lambdas, por que não consideraram isso também para apoiar o "rendimento"?

Certamente eles podem ver o valor das co-rotinas...então só posso supor que eles pensam em implementações baseadas em macro (como Simon Tatham) como um substituto adequado.Eles não o são, no entanto, por vários motivos:estado mantido pelo receptor, não reentrante, baseado em macro (isso por si só é razão suficiente), etc.

Editar: yield não depende de coleta de lixo, fios ou fibras.Você pode ler o artigo de Simon para ver que estou falando sobre o compilador fazendo uma transformação simples, como:

int fibonacci() {
    int a = 0, b = 1;
    while (true) {
        yield a;
        int c = a + b;
        a = b;
        b = c;
    }
}

Em:

struct GeneratedFibonacci {
    int state;
    int a, b;

    GeneratedFibonacci() : state (0), a (0), b (1) {}

    int operator()() {
        switch (state) {
        case 0:
            state = 1;
            while (true) {
                return a;

        case 1:
                int c = a + b;
                a = b;
                b = c;
            }
        }
    }
}

Coleta de lixo?Não.Tópicos?Não.Fibras?Não.Transformação simples?Indiscutivelmente, sim.

Foi útil?

Solução

Eles fizeram isso para lambdas, por que não consideraram isso para apoiar o rendimento também?

Verifica a papéis.Alguém propôs isso?

...Só posso imaginar que eles consideram as implementações baseadas em macro um substituto adequado.

Não necessariamente.Tenho certeza de que eles sabem que essas soluções macro existem, mas substituí-las não é motivação suficiente, por si só, para aprovar novos recursos.


Embora existam vários problemas em torno de uma nova palavra-chave, eles podem ser superados com uma nova sintaxe, como foi feito para lambdas e usando auto como tipo de retorno de função.

Recursos radicalmente novos precisam de drivers fortes (ou seja,pessoas) para analisar completamente e empurrar os recursos através do comitê, pois sempre haverá muitas pessoas céticas em relação a uma mudança radical.Portanto, mesmo na ausência do que você consideraria uma forte razão técnica contra uma construção de rendimento, ainda pode não ter havido apoio suficiente.

Mas, fundamentalmente, a biblioteca padrão C++ adotou um conceito de iteradores diferente do que você veria com rendimento.Compare com os iteradores do Python, que requerem apenas duas operações:

  1. an_iter.next() retorna o próximo item ou gera StopIteration (next() embutido incluído em 2.6 em vez de usar um método)
  2. iter(an_iter) retorna an_iter (para que você possa tratar iteráveis ​​e iteradores de forma idêntica em funções)

Os iteradores do C++ são usados ​​em pares (que devem ser do mesmo tipo), são divididos em categorias, seria uma mudança semântica fazer a transição para algo mais adequado a uma construção de rendimento, e essa mudança não se encaixaria bem com conceitos (que tem desde então foi descartado, mas isso veio relativamente tarde).Por exemplo, veja o justificativa por (justificadamente, embora decepcionantemente) rejeitar meu comentário sobre a alteração de loops for baseados em intervalo para um formato que tornaria muito mais fácil escrever essa forma diferente de iterador.

Para esclarecer concretamente o que quero dizer sobre diferentes formas de iteradores:seu exemplo de código gerado precisa outro type para ser o tipo de iterador mais o maquinário associado para obter e manter esses iteradores.Não que isso não possa ser resolvido, mas não é tão simples quanto você pode imaginar à primeira vista.A verdadeira complexidade é a "transformação simples" respeitando exceções para variáveis ​​"locais" (inclusive durante a construção), controlando a vida útil de variáveis ​​"locais" em escopos locais dentro do gerador (a maioria precisaria ser salva entre chamadas) e assim por diante.

Outras dicas

Não sei dizer por que não adicionaram algo assim, mas no caso dos lambdas, não foram apenas adicionado ao idioma também.

Eles começaram como uma implementação de biblioteca no Boost, o que provou que

  • lambdas são amplamente úteis:muitas pessoas irão usá-los quando estiverem disponíveis, e isso
  • uma implementação de biblioteca em C++03 sofre uma série de deficiências.

Com base nisso, a comissão decidiu adotar algum tipo de lambdas em C++0x, e acredito que inicialmente eles experimentaram adicionar recursos de linguagem mais gerais para permitir um melhorar implementação de biblioteca do que o Boost possui.

E, eventualmente, eles tornaram isso um recurso central da linguagem, porque não tinham outra escolha:porque não foi possível fazer um bom o bastante implementação da biblioteca.

Novos recursos básicos da linguagem não são simplesmente adicionados à linguagem porque parecem uma boa ideia.O comitê é muito relutante em adicioná-los, e o recurso em questão realmente precisa provar a si mesmo.Deve ser demonstrado que o recurso é:

  • possível implementar no compilador,
  • vai resolver uma necessidade real, e
  • que uma implementação de biblioteca não seria boa o suficiente.

No caso se um yield palavra-chave, sabemos que o primeiro ponto pode ser resolvido.Como você mostrou, é uma transformação bastante simples que pode ser feita mecanicamente.

O segundo ponto é complicado.Quanto de um precisar pois isso existe?Quão amplamente utilizadas são as implementações de biblioteca existentes?Quantas pessoas pediram isso ou apresentaram propostas nesse sentido?

O último ponto parece passar também.Pelo menos em C++03, uma implementação de biblioteca sofre algumas falhas, como você apontou, que poderia justificar uma implementação de linguagem central.Uma implementação melhor da biblioteca poderia ser feita em C++ 0x?

Portanto, suspeito que o principal problema seja realmente a falta de interesse.C++ já é uma linguagem enorme e ninguém quer que ela cresça a menos que os recursos adicionados sejam realmente Vale a pena.Suspeito que isso não seja útil o suficiente.

Adicionar uma palavra-chave é sempre complicado porque invalida o código anteriormente válido.Você tenta evitar isso em uma linguagem com uma base de código tão grande quanto C++.

A evolução do C++ é um processo público.Se você sente yield deveria estar lá, formule uma solicitação apropriada ao comitê de padrão C++.

Você receberá sua resposta diretamente das pessoas que tomaram a decisão.

Portanto, parece que não chegou ao C++ 11 ou C++ 14, mas pode estar a caminho do C++ 17.Confira a palestra C++ Coroutines, uma abstração de sobrecarga negativa da CppCon2015 e do jornal aqui.

Para resumir, eles estão trabalhando para estender funções C++ para ter rendimento e espera como recursos de funções.Parece que eles têm uma implementação inicial no Visual Studio 2015, não tenho certeza se o clang ainda tem uma implementação.Além disso, parece que pode haver alguns problemas com o uso de rendimento e espera como palavras-chave.

A apresentação é interessante porque ele fala sobre o quanto simplificou o código de rede, onde você fica esperando a entrada dos dados para continuar a sequência de processamento.Surpreendentemente, parece que o uso dessas novas corrotinas resulta em código mais rápido/menos do que seria feito hoje.É uma ótima apresentação.

A proposta de funções recuperáveis ​​para C++ pode ser encontrada aqui.

Em geral, você pode acompanhar o que está acontecendo pelo documentos do comitê, embora seja melhor acompanhar em vez de procurar um problema específico.

Uma coisa a lembrar sobre o comitê C++ é que ele é um comitê voluntário e não pode realizar tudo o que deseja.Por exemplo, não havia mapa do tipo hash no padrão original, porque eles não conseguiram fazê-lo a tempo.Pode ser que não houvesse ninguém na comissão que se importasse o suficiente com yield e o que ele faz para garantir que o trabalho seja feito.

A melhor maneira de descobrir seria perguntar a um membro ativo do comitê.

Bem, para um exemplo tão trivial como esse, o único problema que vejo é que std::type_info::hash_code() não está especificado constexpr.Acredito que uma implementação em conformidade ainda poderia fazer isso e apoiar isso.De qualquer forma, o verdadeiro problema é obter identificadores únicos, por isso pode haver outra solução.(Obviamente, peguei emprestada sua construção de "interruptor mestre", obrigado.)

#define YIELD(X) do { \
    constexpr size_t local_state = typeid([](){}).hash_code(); \
    return (X); state = local_state; case local_state: ; } \
while (0)

Uso:

struct GeneratedFibonacci {
    size_t state;
    int a, b;

    GeneratedFibonacci() : state (0), a (0), b (1) {}

    int operator()() {
        switch (state) {
        case 0:
            while (true) {
                YIELD( a );
                int c = a + b;
                a = b;
                b = c;
            }
        }
    }
}

Hmm, eles também precisariam garantir que o hash não seja 0.Não há nada demais nisso também.E um DONE macro é fácil de implementar.


O verdadeiro problema é o que acontece quando você retorna de um escopo com objetos locais.Não há esperança de salvar um quadro de pilha em uma linguagem baseada em C.A solução é usar uma corrotina real, e o C++ 0x aborda isso diretamente com threads e futuros.

Considere este gerador/corrotina:

void ReadWords() {
    ifstream f( "input.txt" );

    while ( f ) {
        string s;
        f >> s;
        yield s;
    }
}

Se um truque semelhante for usado para yield, f é destruído no primeiro yield, e é ilegal continuar o loop depois dele, porque você não pode goto ou switch após uma definição de objeto não-POD.

houve várias implementações de corrotinas como bibliotecas de espaço do usuário.No entanto, e aqui está o acordo, essas implementações dependem de detalhes não padronizados.Por exemplo, em nenhum lugar do padrão C++ é especificado como os quadros de pilha são mantidos.A maioria das implementações apenas copia a pilha porque é assim que a maioria das implementações de c++ funciona

em relação aos padrões, o c++ poderia ter ajudado no suporte à rotina, melhorando a especificação dos stack frames.

Na verdade, 'adicioná-lo' à linguagem não me parece uma boa ideia, porque isso lhe daria uma implementação 'boa o suficiente' para a maioria dos casos que é inteiramente dependente do compilador.Para os casos em que o uso de uma corrotina é importante, isso não é aceitável de qualquer maneira

concordo primeiro com @Potatoswatter.

Suportar corrotina não é a mesma coisa que suportar lambdas e não isso transformação simples como jogado com o dispositivo de Duff.

Você precisa corrotinas assimétricas completas (stackful) para funcionar como geradores em Python.A implementação de Simon Tatham e Chris' ambos estão sem pilha enquanto Boost.Coroutine é empilhado, embora seja pesado.

Infelizmente, o C++ 11 ainda não tem yield para corrotinas ainda, talvez C++ 1y;)

PS:Se você realmente gosta de geradores no estilo Python, dê uma olhada em esse.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top