2010-10-05 19 views
30

He estado usando rendimiento en muchos de mis programas Python, y realmente borra el código en muchos casos. I blogged about it y es una de las páginas más populares de mi sitio.¿Por qué no se agregó el rendimiento a C++ 0x?

C# también ofrece rendimiento - que se implementa a través del estado de mantenimiento en el lado del llamante, hecho a través de una clase generada automáticamente que mantiene el estado, las variables locales de la función, etc.

Actualmente estoy leyendo sobre C++ 0x y sus adiciones; y al leer acerca de la implementación de lambdas en C++ 0x, descubrí que también se realizó mediante clases generadas automáticamente, equipadas con operador() que almacena el código lambda. La pregunta natural se formó en mi mente: lo hicieron por lambdas, ¿por qué no lo consideraron también para apoyar el "rendimiento"?

Seguramente pueden ver el valor de las rutinas conjuntas ... así que solo puedo adivinar que piensan que las implementaciones basadas en macros (como Simon Tatham's) son un sustituto adecuado. No son, sin embargo, por muchas razones: destinatario de la llamada cuidada estado, no reentrante, con sede en macro (que por sí solo es motivo suficiente), etc.

Editar: hilosyield no depende de la recolección de basura, o fibras Puede leer el artículo de Simon al ver que yo estoy hablando del compilador de hacer una transformación sencilla, como por ejemplo:

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

En:

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; 
      } 
     } 
    } 
} 

basura recogida? No. ¿Hilos? No. ¿Fibras? No. ¿Transformación simple? Podría decirse que sí.

+1

Tenga en cuenta que "todo se puede hacer" en idiomas como C y C++, simplemente porque es fácil de emular manualmente para un ejemplo simple, no hace que sea fácil incorporarlo a la sintaxis. Tome Boost por ejemplo, hace cosas locas con C++, pero las longitudes que toman son locas detrás de escena. Incluso cuando las ideas en Boost se incorporan en C++ estándar, se implementan de manera completamente diferente. (Eche un vistazo a las cosas de unique_ptr, lambdas y parámetros variables) –

+6

Porque nadie escribió una propuesta oficial para ello. – sellibitze

+1

Su transformación original del generador tenía un error: nunca progresó al estado 1. Se corrigió eso y se aplicó el dispositivo de Duff. –

Respuesta

7

Lo hicieron por lambdas, ¿por qué no lo consideraron para apoyar el rendimiento, también?

Compruebe papers. ¿Alguien lo propuso?

... Solo puedo adivinar que consideran que las implementaciones basadas en macros son un sustituto adecuado.

No necesariamente. Estoy seguro de que saben que existen tales macro soluciones, pero reemplazarlas no es suficiente motivación, por sí solo, para que se pasen las nuevas funciones.


A pesar de que hay varias cuestiones en torno a una nueva palabra clave, los que podrían superarse mediante una nueva sintaxis, tal como se hizo para lambdas y el uso de automóviles como un tipo de retorno de la función.

Las funciones radicalmente nuevas necesitan controladores fuertes (es decir, personas) para analizar y enviar por completo características a través del comité, ya que siempre habrá mucha gente escéptica ante un cambio radical. De modo que, incluso sin lo que vería como una fuerte razón técnica en contra de una construcción de rendimiento, puede que aún no haya habido suficiente apoyo.

Pero, fundamentalmente, la biblioteca estándar de C++ ha adoptado un concepto diferente de iteradores de lo que vería con el rendimiento. Comparar a los iteradores de Python, que sólo requieren dos operaciones:

  1. an_iter.next() devuelve el siguiente elemento o plantea StopIteration (next() BuiltIn incluido en 2.6 en lugar de utilizar un método)
  2. iter (an_iter) devuelve an_iter (para que pueda tratar iterables e iteradores de forma idéntica en funciones)

iteradores C++ 's se utilizan en parejas (que debe ser del mismo tipo), se dividen en categorías, sería un cambio semántico para la transición en algo más susceptible a una construcción de rendimiento, y ese cambio no encajaría bien con los conceptos (que luego se abandonó, pero llegó tarde) Por ejemplo, vea el rationale para (justificadamente, si decepciona) rechazar mi comentario sobre el cambio de bucles basados ​​en rangos a un formulario que haría mucho más fácil escribir esta forma diferente de iterador.

Para aclarar concretamente lo que quiero decir acerca de las diferentes formas de iterador: el ejemplo de código generada necesita otro tipo ser el tipo más iterador maquinaria asociada para conseguir y mantener los iteradores. No es que no pueda ser manejado, pero no es tan simple como puedes imaginar al principio. La verdadera complejidad es la "transformación simple" que respeta excepciones para variables "locales" (incluso durante la construcción), el control de la vida útil de variables "locales" en ámbitos locales dentro del generador (la mayoría debería guardarse en llamadas), etc.

+4

Dirigiéndose a: "Verifique los documentos. ¿Alguien lo propuso?" - Bueno, si nadie en el comité de C++ se da cuenta de los beneficios de corutinas/rendimiento (en, por ejemplo, trabajo asincrónico óptimo en servidores de socket de servicio pesado, consultas al estilo LINQ, etc.), no es de extrañar que otros idiomas se arraiguen, condenando C++ a la extinción gradual. En cuanto a los iteradores, no son necesariamente parte de esta discusión: el rendimiento se mantiene solo sin iteradores, como una forma diferente de devolver los resultados. Ya no tengo espacio para comentarios, y estoy muy convencido de que incluso las cosas obvias, como el rendimiento, están "fuera del alcance" del pensamiento del comité. – ttsiodras

+4

@ttsiodras: Mire esos documentos alguna vez, y las cosas que entran en ellos. No mucho está fuera del alcance del pensamiento del comité. Si te unes al Comité y presionas por algo como 'rendimiento 'y trabajas duro para que funcione en el idioma y escuches las objeciones de otras personas, sería en el siguiente estándar. Es como la función de mascota de todos los demás que no se incluyó. –

+3

@ttsiodras: los iteradores son una característica central de la biblioteca estándar, pero se tratan como una característica del núcleo del lenguaje (por ejemplo, observe el bucle for-the-based de 0x, y también por qué se diseñaron como un superconjunto de punteros). Creo que cualquier propuesta de generadores/rendimiento que no sea compatible con el concepto de iteradores de C++ debería revisarse para que así sea. –

8

Agregar una palabra clave siempre es complicado, porque invalida el código previamente válido. Intenta evitar eso en un lenguaje con una base de código tan grande como C++.

La evolución de C++ es un proceso público. Si siente que yield debe estar allí, formule una solicitud apropiada al comité estándar de C++.

Recibirá su respuesta, directamente de las personas que tomaron la decisión.

+0

Esto es muy creíble. El comité de ECMAScript hizo todo lo posible para no introducir una nueva palabra clave. EX: 'Object.defineProperty' y' "use strict"; ' – ChaosPandion

+1

Escribir la propuesta sería genial, pero es demasiado tarde para obtener dicha característica en 0x. –

+0

Esto podría haberse agregado sin una palabra clave, como una función de biblioteca. Entonces -1. – einpoklum

2

En general, puede realizar un seguimiento de lo que está sucediendo en el committee papers, aunque es mejor para realizar un seguimiento en lugar de buscar un problema específico.

Una cosa para recordar sobre el comité de C++ es que es un comité de voluntarios y no puede lograr todo lo que quiere. Por ejemplo, no había un mapa tipo hash en el estándar original, porque no pudieron llegar a tiempo. Puede ser que no haya nadie en el comité que se haya preocupado lo suficiente por yield y lo que hace para asegurarse de que el trabajo haya terminado.

La mejor manera de averiguarlo sería consultar a un miembro activo del comité.

+1

Tengo una especie de prejuicio contra los comités: es mi "sensación" que los lenguajes diseñados por comités generalmente son ... problemáticos. Tendría razón al argumentar que debería dejar C++ y C++ 0x detrás de mí, y centrarme en Python y Perl y otros lenguajes "dictadores benévolos" ... pero no puedo. Lo que realmente me molestó y me hizo escribir esta pregunta para SO, fue que * hicieron * el trabajo para lambdas, y no dieron un pequeño paso para hacerlo también para co-rutinas ... (suspiro) .. . C++, siempre algunos pasos detrás de otros ... (con la excepción de las plantillas) – ttsiodras

+0

@ttsiodras: Mis 2 idiomas son Python y C. C es solo el mínimo, no te absorbe ninguna estupidez, y está ardiendo rápidamente y puede hacer cualquier cosa. El otro es Python, por las razones que has dado. Un hombre tiene su dedo en el pulso, y me gusta su forma de pensar. C++ es un desastre. –

+1

@ttsiodras: si tiene un verdadero prejuicio contra los comités, puede canjear desde C++ y crear su propio idioma. Si eres * muy * bueno y proporcionas algo útil, atraerás a otras personas que utilizarán el idioma que has creado. Después de un tiempo, puede decidir que algunas de estas otras personas también tengan buenas ideas sobre cómo debería evolucionar el lenguaje que ha creado; invita a algunos de ellos para que te ayuden, y tienes un comité. Aaaargh! :-) Los comités no tienen nada que temer * per se * pero no todos son iguales; algunos realmente apestan –

2

Bueno, para un ejemplo tan trivial como ese, el único problema que veo es que std::type_info::hash_code() no está especificado constexpr. Creo que una implementación conforme podría hacerlo y apoyar esto. De todos modos, el verdadero problema es obtener identificadores únicos, por lo que podría haber otra solución. (Obviamente me prestó su "interruptor maestro" construir, gracias.)

#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, ellos también tienen que garantizar que el hash no es 0. No hay problema allí tampoco . Y una macro DONE es fácil de implementar.


El problema real es lo que sucede cuando regresas de un osciloscopio con objetos locales. No hay esperanza de guardar un marco de pila en un lenguaje basado en C. La solución es usar una coroutine real, y C++ 0x aborda directamente eso con hilos y futuros.

Considere este generador/corrutina:

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

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

Si un truco similar se utiliza para yield, f es destruido en la primera yield, y es ilegal para continuar el bucle después de ella, porque no se puede goto o switch después de una definición de objeto no POD.

+1

Gracias por responder, pero no me está diciendo nada que yo no supiera ya - sí , podemos emular algunos aspectos del rendimiento con macros, y Simon (el artículo vinculado en mi texto original) lo hizo hace años. El problema es que esto no es suficiente, como tanto yo como usted señalamos. Y dado que C++ 0x agregó soporte para lambdas agregando generación automática de clases al compilador, y "yield" requeriría exactamente lo mismo, solo lamento su ausencia. Eso es todo. – ttsiodras

+1

@ttsiodras: No, "rendimiento" requiere guardar y restaurar el marco de la pila, o lo que es lo mismo, una coroutine. En C idiomas que es equivalente a enhebrar. En C++, si desea un objeto generador con esa sintaxis, debe engendrar un hilo y sincronizarlo. Y C++ 0x agregó soporte nativo para eso. – Potatoswatter

+0

¡Hmm, acabo de recibir un voto favorable, 2 años después!A simple vista, 'typeid ([]() {})' no es una forma válida de obtener un número aleatorio en tiempo de compilación. Sin embargo, tal vez mi respuesta de contadores en tiempo de compilación funcionaría. – Potatoswatter

1

ha habido varios implementación de corrutinas como bibliotecas de espacio de usuario. Sin embargo, y este es el trato, esas implementaciones se basan en detalles no estándar. Por ejemplo, en ninguna parte del estándar C++ se especifica cómo se guardan los cuadros de pila. La mayoría de las implementaciones sólo tienes que copiar la pila porque así es como trabaja la mayoría de las implementaciones ++ c

respecto a las normas, C++ podrían haber ayudado apoyo corrutina mediante la mejora de la especificación de los marcos de pila.

En realidad, 'agregarlo' al lenguaje no me parece una buena idea, porque eso lo dejaría con una implementación 'lo suficientemente buena' para la mayoría de los casos que es completamente dependiente del compilador. Para los casos en que el uso de una corutina importa, esto no es aceptable de todos modos

22

No puedo decir por qué no agregaron algo como esto, pero en el caso de lambdas, no fueron solo agregado a la idioma tampoco.

Se comenzó su vida como una implementación de la biblioteca en Boost, que demostraron que

  • lambdas son ampliamente útil: mucha gente va a usar cuando están disponibles, y que
  • una implementación de la biblioteca de C++ 03 adolece de varias deficiencias.

Basado en esto, el Comité decidió adoptar algún tipo de lambdas en C++ 0x, y creo que inicialmente experimentado con la adición de más características del lenguaje general como para permitir una implementación mejor biblioteca que tiene Boost .

Y, finalmente, lo hicieron una característica del lenguaje central, porque no tenían otra opción: porque no era posible hacer una implementación de la biblioteca suficientemente buena.

Las nuevas funciones del lenguaje central no se agregan simplemente al idioma porque parecen una buena idea. El comité es muy reacio a agregarlos, y la característica en la pregunta realmente necesita probarse. Se debe demostrar que la función es:

  • posible implementar en el compilador,
  • va a resolver una necesidad real, y
  • que una implementación de la biblioteca no sería lo suficientemente bueno.

En el caso de una palabra clave yield, sabemos que el primer punto se puede resolver. Como ha demostrado, es una transformación bastante simple que se puede hacer mecánicamente.

El segundo punto es complicado.¿Cuánto de necesita para esto está allí? ¿Cuán ampliamente utilizados están las implementaciones de la biblioteca que existen? ¿Cuántas personas han pedido esto o han enviado propuestas para ello?

El último punto parece pasar también. Al menos en C++ 03, la implementación de una biblioteca adolece de fallas, como usted señaló, que podría justificar una implementación de lenguaje central. Sin embargo, ¿se podría hacer una mejor implementación de la biblioteca en C++ 0x?

Así que sospecho que el problema principal es realmente una falta de interés. C++ ya es un lenguaje enorme, y nadie quiere que crezca a menos que las características que se agregan sean realmente vale la pena. Sospecho que esto no es lo suficientemente útil.

+0

Las extensiones de idioma pueden funcionar sin demasiado dolor. En el compilador Haskell GHC, hay un número increíble de extensiones de lenguaje, y funcionan bien porque se definen como transformaciones en un lenguaje teórico, simple e intermedio. Eso significa que la semántica es directa para razonar, aunque puede haber muchas campanas y silbidos con respecto a la sintaxis. – user239558

+0

@ user239558: por supuesto que pueden. Pero eso realmente no resuelve los problemas que describí – jalf

0

primero con @Potatoswatter.

Para apoyar coroutine no es lo mismo que soporte para lambdas y no es que simple transformación como jugado con el dispositivo de Duff.

Necesita full asymmetric coroutines (stackful) para funcionar como generadores en Python. La implementación de Simon Tatham's y Chris' no son apilables, mientras que Boost.Coroutine es muy compacta, aunque es pesada.

Desafortunadamente, C++ 11 todavía no tienen yield para corrutinas aún, tal C++ 1a;)

PD: Si realmente te gusta generadores de estilo de Python, echar un vistazo a this.

7

Parece que no se convirtió en C++ 11 o C++ 14, pero podría estar en camino a C++ 17. Eche un vistazo a la conferencia C++ Coroutines, a negative overhead abstraction de CppCon2015 y el documento here.

En resumen, están trabajando para extender las funciones de C++ para tener rendimiento y esperar como características de las funciones. Parece que tienen una implementación inicial en Visual Studio 2015, no estoy seguro de si clang todavía tiene una implementación. También parece que puede haber algunos problemas con el uso del rendimiento y esperar como palabras clave.

La presentación es interesante porque habla sobre cuánto simplificó el código de red, donde está esperando que ingresen los datos para continuar la secuencia de procesamiento. Sorprendentemente, parece que usar estos nuevos resultados de corutinas en un código más rápido/menos de lo que se haría hoy. Es una gran presentación.

La propuesta de funciones reanudables para C++ se puede encontrar en here.

+1

Ver también http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0099r0.pdf y http://open-std.org/ JTC1/SC22/WG21/docs/papers/2015/p0114r0.pdf y http://open-std.org/JTC1/SC22/WG21/docs/papers/2015/p0158r0.html –

Cuestiones relacionadas