¿Escribir en C para rendimiento? [cerrado]

32

Sé que he oído con bastante frecuencia que C generalmente tiene una ventaja de rendimiento sobre C ++. Realmente no pensé en nada más hasta que me di cuenta de que MSVC ni siquiera parece admitir el estándar más nuevo de C, pero el más nuevo lo admite C99 (que yo sepa).

Estaba planeando escribir una biblioteca con algún código para renderizar en OpenGL para poder reutilizarla. Estaba planeando escribir la biblioteca en C ya que cualquier aumento de rendimiento es bienvenido cuando se trata de gráficos.

¿Pero realmente valdría la pena? El código que usa la biblioteca probablemente se escribiría en C ++ y prefiero codificar en C ++ en general.

Sin embargo, si produjera incluso una pequeña diferencia en el rendimiento, probablemente iría con C.

También cabe señalar que esta biblioteca sería algo que haría que funcione en Windows / OS X / Linux, y probablemente compile todo de forma nativa (MSVC para Windows, Clang o GCC para OS X, y GCC para Linux .. .o posiblemente los compiladores de Intel para todo).

He mirado a mi alrededor y he encontrado algunos puntos de referencia y demás, pero todo lo que he visto ha tratado con GCC en lugar de MSVC y Clang. Además, los puntos de referencia no mencionan los estándares de los idiomas utilizados. ¿Alguno tiene alguna idea sobre esto?

EDITAR:Solo quería compartir mi punto de vista sobre esta pregunta después de un par de años más de experiencia. Terminé escribiendo el proyecto por el que hacía esta pregunta en C ++. Comencé otro proyecto más o menos al mismo tiempo en C, ya que estábamos buscando obtener una pequeña cantidad de rendimiento que pudiéramos y necesitábamos que el proyecto pudiera vincularse en C. Hace un par de meses, llegué al punto en el que realmente necesitaba mapas y avancé. manipulación de cuerdas. Conocía las habilidades para esto en la biblioteca estándar de C ++ y finalmente llegué a la conclusión de que esas estructuras en la biblioteca estándar probablemente superarían y serían más estables que los mapas y cadenas que podría implementar en C en un período de tiempo razonable. El requisito de ser enlazable en C se cumplió fácilmente escribiendo una interfaz C en el código C ++, que se realizó rápidamente con tipos opacos. Reescribir la biblioteca en C ++ parecía ir mucho más rápido que cuando se escribía en C y era menos propenso a errores, especialmente a pérdidas de memoria. También pude usar la biblioteca de subprocesos de biblioteca estándar, que ha sido mucho más fácil que usar implementaciones específicas de la plataforma. Al final, creo que escribir la biblioteca en C ++ generó grandes beneficios, posiblemente con un pequeño costo de rendimiento. Todavía no he comparado la versión de C ++, pero creo que incluso es posible que haya ganado algo de rendimiento al usar estructuras de datos de biblioteca estándar que las que escribí. Creo que escribir la biblioteca en C ++ generó grandes beneficios, posiblemente con un pequeño costo de rendimiento. Todavía no he comparado la versión de C ++, pero creo que incluso es posible que haya ganado algo de rendimiento al usar estructuras de datos de biblioteca estándar que las que escribí. Creo que escribir la biblioteca en C ++ generó grandes beneficios, posiblemente con un pequeño costo de rendimiento. Todavía no he comparado la versión de C ++, pero creo que incluso es posible que haya ganado algo de rendimiento al usar estructuras de datos de biblioteca estándar que las que escribí.

danielunderwood
fuente
9
El último soporte de MSVC es en realidad C89.
Desvío
44
@detly En Visual Studio 2013, la gran mayoría de las características de C99 son compatibles . No es un soporte completo, pero apuesto a que en la práctica está bien usarlo para escribir C99.
congusbongus
44
@ danielu13 - ¿Dónde escuchó exactamente que C tiene una ventaja de rendimiento sobre C ++?
Ramhound
1
@ Sebastian-LaurenţiuPlesciuc No creo que esos enlaces sean realmente útiles. El primero podría contrarrestarse con casi la misma pregunta programmers.stackexchange.com/q/113295/76444 pero a favor de c ++ en lugar de c como en su enlace. Para su segundo enlace, es solo una diatriba de linus torvalds. Espero que todos sepan ahora que realmente ama odiar a C ++ y que no lo tocaría con un palo, pero sus quejas sobre c ++ no son objetivas, están llenas de opinión personal y prejuicios y realmente no reflejan la realidad del lenguaje. . Al menos esa es mi opinión .
user1942027
1
@RaphaelMiedl También mencioné que esto fue escrito en 2007, hace mucho tiempo, los compiladores de C ++ y el lenguaje C ++ han evolucionado desde entonces. En cualquier caso, depende del programador elegir qué idioma usar.
Sebastian-Laurenţiu Plesciuc

Respuestas:

89

Supongo que las personas a menudo afirman que C es más rápido que C ++ porque es más fácil razonar sobre el rendimiento en C. C ++ no es inherentemente más lento o más rápido, pero cierto código de C ++ puede ocultar las sanciones ocultas de rendimiento. Por ejemplo, puede haber copias y conversiones implícitas que no son visibles de inmediato cuando se observa algún fragmento de código C ++.

Tomemos la siguiente declaración:

foo->doSomething(a + 5, *c);

Supongamos además que doSomethingtiene la siguiente firma:

void doSomething(int a, long b);

Ahora, tratemos de analizar el posible impacto en el rendimiento de esta declaración en particular.

En C, las implicaciones son bastante claras. foosolo puede ser un puntero a una estructura y doSomethingdebe ser un puntero a una función. *cdesreferencia un largo, y a + 5es la suma entera. La única incertidumbre proviene del tipo de a: Si no es un int, habrá alguna conversión. pero aparte de eso, es fácil cuantificar el impacto en el rendimiento de esta declaración única.

Ahora pasemos a C ++. La misma declaración ahora puede tener características de rendimiento muy diferentes:

  1. doSomethingpodría ser una función miembro no virtual (barata), función miembro virtual (un poco más cara) std::function, lambda ... etc. Lo que es peor, foopodría ser una sobrecarga de tipo de clase operator->con alguna operación de complejidad desconocida. Entonces, para cuantificar el costo de llamardoSomething , ahora es necesario conocer la naturaleza exacta de fooy doSomething.
  2. apodría ser un número entero, o una referencia a un número entero (indirección adicional), o un tipo de clase que implementa operator+(int). El operador podría incluso devolver otro tipo de clase que es implícitamente convertible a int. Nuevamente, el costo de rendimiento no es aparente solo en la declaración.
  3. cpodría ser un tipo de implementación de clase operator*(). También podría ser una referencia a un long*etc.

Te dan la imagen. Debido a las características del lenguaje de C ++, es mucho más difícil cuantificar los costos de rendimiento de una sola declaración que en C. Ahora, además, abstracciones comostd::vector , std::stringse usan comúnmente en C ++, que tienen sus propias características de rendimiento, y ocultan las asignaciones de memoria dinámica ( ver también la respuesta de @ Ian).

Entonces, la conclusión es: en general, no hay diferencia en el rendimiento posible que se puede lograr usando C o C ++. Pero para un código realmente crítico para el rendimiento, las personas a menudo prefieren usar C porque hay menos penalizaciones de rendimiento ocultas posibles .

guitarra letal
fuente
1
Excelente respuesta. Esto es a lo que me refería en mi respuesta, pero usted lo ha explicado mucho mejor.
Ian Goldby
44
Esta realmente debería ser la respuesta aceptada. Explica por qué existen declaraciones como "C es más rápido que C ++". C puede ser más rápido o más lento que C ++, pero generalmente es mucho más fácil descubrir por qué una pieza específica de código C es rápida / lenta, lo que generalmente también hace que sea más fácil de optimizar.
Leo
No tomaremos nada de esta excelente respuesta (para la cual obtuve un +1), pero el compilador (es) puede ser las manzanas y las naranjas en esta comparación. Pueden generar un código idéntico para C frente a C ++, o no. Por supuesto, se puede decir lo mismo para cualquiera de los dos compiladores u opciones de compilador, incluso cuando se compila físicamente el mismo programa con los mismos supuestos del lenguaje fuente.
JRobert
44
Agregaría que la mayoría de los tiempos de ejecución de C ++ son masivos en comparación con el tiempo de ejecución de C equivalente, lo que sería importante si tiene limitaciones de memoria.
James Anderson
@JamesAnderson Si realmente tienes esa memoria limitada, probablemente no necesites tiempo de ejecución :)
Navin
30

El código escrito en C ++ puede ser más rápido que en C, para ciertos tipos de tareas.

Si prefiere C ++, use C ++. Cualquier problema de rendimiento será insignificante en comparación con las decisiones algorítmicas de su software.

whatsisname
fuente
66
Puede ser más rápido pero podría no serlo por la misma razón.
Rob
¿Puedes dar algunos ejemplos de código optimizado escrito en C ++ que sea más rápido que C optimizado?
1
@TomDworzanski: un ejemplo es que al usar plantillas, las decisiones sobre las rutas de código se pueden determinar en el momento de la compilación y terminar codificadas en el binario final, en lugar de condicionales y ramificaciones, como se requeriría si se escribiera en c, así como la capacidad para evitar llamadas a funciones a través de la alineación.
cuál es el
23

Uno de los principios de diseño de C ++ es que no paga por las funciones que no usa. Entonces, si escribe código en C ++ y evita características que no existen en C, entonces el código compilado resultante debería ser equivalente en rendimiento (aunque tendría que medir esto).

Hay un costo insignificante para usar clases, por ejemplo, en comparación con estructuras y un montón de funciones asociadas. Las funciones virtuales costarán un poco más, y tendría que medir el rendimiento para ver si es importante para su aplicación. Lo mismo ocurre con cualquier otra característica del lenguaje C ++.

Greg Hewgill
fuente
3
La sobrecarga del despacho de funciones virtuales es casi insignificante, a menos que se haya excedido en descomponerse y hacer que las cosas sean virtuales. Las vtables serán pequeñas en comparación con el resto de su código y datos, y la rama indexada a través de la vtable agrega algunos relojes a cada invocación de rutina. Dado que las invocaciones de rutina, todo, la llamada para regresar, será de unos pocos cientos a unos pocos millones de relojes, la rama vtable estará enterrada en el piso de ruido.
John R. Strohm
66
Las estructuras son clases en C ++.
derecha
2
@rightfold: Claro, pero aún puede escribir código C ++ que pase punteros a estructuras sin usar la thisfunción de lenguaje de puntero. Eso es todo lo que estaba diciendo.
Greg Hewgill
44
@John El costo real no es la indirección (aunque estoy bastante seguro de que esto también afectará un poco a la captación previa de algunos procesadores), sino el hecho de que no se pueden alinear funciones virtuales (al menos en C ++) que no permite muchas otras posibles optimizaciones Y sí, eso puede tener una influencia gigantesca en el rendimiento.
Voo
2
@Voo Para ser justos, se puede decir lo mismo sobre el código C equivalente (código que emula manualmente el polimorfismo de tiempo de ejecución). La mayor diferencia es que creo que sería más fácil para un compilador determinar si dicha función podría incorporarse en C ++.
Thomas Eding
14

Una razón por la que los lenguajes de nivel superior a veces son más lentos es porque pueden ocultar mucho más administración de memoria que los idiomas de nivel inferior.

Cualquier lenguaje (o biblioteca, API, etc.) que abstraiga detalles de bajo nivel puede estar ocultando operaciones costosas. Por ejemplo, en algunos idiomas, simplemente recortar los espacios en blanco al final de una cadena da como resultado una asignación de memoria y una copia de la cadena. La asignación y copia de memoria en particular puede ser costosa si ocurren repetidamente en un ciclo cerrado.

Si escribieras este tipo de código en C, sería evidente. En C ++ posiblemente menos, porque las asignaciones y la copia podrían abstraerse en una clase en algún lugar. Incluso podrían estar ocultos detrás de un operador o constructor de copia sobrecargado de aspecto inocente.

Así que usa C ++ si quieres. Pero no se deje engañar por la aparente conveniencia de las abstracciones cuando no sabe qué hay debajo de ellas.

Por supuesto, use un generador de perfiles para descubrir qué es lo que realmente está ralentizando su código.

Ian Goldby
fuente
5

Por lo que vale, tiendo a escribir mis bibliotecas en C ++ 11 para el conjunto de características mejoradas. Me gusta poder aprovechar cosas como punteros compartidos, excepciones, programación genérica y otras características exclusivas de C ++. Me gusta C ++ 11 porque he descubierto que buena parte de él es compatible con todas las plataformas que me interesan. Visual Studio 2013 tiene mucho de las funciones principales del lenguaje e implementaciones de la biblioteca listas para funcionar y supuestamente está trabajando para agregar el resto. Como bien sabes, Clang y GCC también admiten todo el conjunto de características.

Dicho esto, recientemente leí acerca de una estrategia realmente excelente con respecto al desarrollo de la biblioteca que creo que es directamente relevante para su consulta. El artículo se titula "Estilo de manejo de errores de CA que funciona bien con las excepciones de C ++" Stefanu Du Toit se refiere a esta estrategia como un patrón de "reloj de arena". El primer párrafo del artículo:

He escrito una gran cantidad de código de biblioteca usando lo que llamo un patrón de "reloj de arena": implemento una biblioteca (en mi caso, típicamente, usando C ++), la envuelvo en una API de C que se convierte en el único punto de entrada a la biblioteca, luego envuelva esa API de C en C ++ o en algún otro lenguaje (s) para proporcionar una rica abstracción y una sintaxis conveniente. Cuando se trata de código multiplataforma nativo, las API de C proporcionan una estabilidad ABI incomparable y portabilidad a otros idiomas a través de FFI. Incluso restrinjo la API a un subconjunto de C que sé que es portátil para una amplia variedad de FFI y aísla a la biblioteca de fugas de cambios en las estructuras de datos internas; espere más sobre eso en futuras publicaciones de blog.


Ahora para abordar su principal preocupación: el rendimiento.

Sugeriría, como muchas de las otras respuestas aquí, que escribir código en cualquier idioma funcionaría igual de bien desde el punto de vista del rendimiento. Desde un punto de vista personal, creo que escribir el código correcto en C ++ es más fácil debido a las características del lenguaje, pero creo que es una preferencia personal. De cualquier manera, los compiladores son realmente inteligentes y tienden a escribir mejor código que usted de todos modos. Es decir que el compilador probablemente optimizará su código mejor que usted.

Sé que muchos programadores dicen esto, pero lo primero que debes hacer es escribir tu código, luego perfilarlo y hacer optimizaciones donde tu perfilador te sugiera que lo hagas. Dedicará mucho más tiempo a producir funciones y luego a optimizarlas una vez que pueda ver dónde están sus cuellos de botella.


Ahora, para algunas lecturas divertidas sobre cómo las funciones y optimizaciones del lenguaje realmente pueden funcionar a su favor:

std :: unique_ptr tiene cero sobrecarga

constexp permite el cálculo en tiempo de compilación

mover semántica evita objetos temporales innecesarios

vmrob
fuente
std::unique_ptr has zero overheadEsto no puede ser cierto (técnicamente hablando) porque tiene que llamar a su constructor si la pila se desenrolla debido a una excepción. Un puntero sin formato no tiene esta sobrecarga y sigue siendo correcto si su código probablemente no se lanzará. Un compilador no podrá probar esto en el caso general.
Thomas Eding
2
@ThomasEding Me refería al tamaño y la sobrecarga de tiempo de ejecución con respecto al código sin excepciones. Corríjame si me equivoco, pero hay modelos de ejecución que generan una sobrecarga de tiempo de ejecución cero cuando no se lanzan excepciones que aún permiten propagar excepciones cuando sea necesario. Aun así, ¿cuándo podría lanzarse una excepción en el constructor de unique_ptr? Está declarado noexcept, y por lo menos maneja todas las excepciones, pero no puedo imaginar qué tipo de excepción podría lanzarse en primer lugar.
vmrob
vmrob: Disculpe ... quise escribir "destructor" en lugar de "constructor". También quise escribir "probablemente no arrojará" también. Eeek!
Thomas Eding
2
@ThomasEding Sabes, no creo que importe si el destructor lanzó una excepción. Mientras el destructor no introduzca nuevas excepciones, sigue siendo cero destrucción general. Además, creo que todo el destructor se alinea en una sola llamada de eliminación / gratuita con optimizaciones.
vmrob
4

La diferencia de rendimiento entre C ++ y C no se debe a nada en el lenguaje, estrictamente hablando, sino a lo que te tienta a hacer. Es como una tarjeta de crédito frente a efectivo. No te hace gastar más, pero lo haces de todos modos, a menos que seas muy disciplinado.

Aquí hay un ejemplo de un programa escrito en C ++, que luego se ajustó agresivamente. Debe saber cómo realizar ajustes de rendimiento agresivos, independientemente del idioma. El método que uso es una pausa aleatoria, como se muestra en este video .

Los tipos de cosas costosas que C ++ te tienta a hacer son la administración excesiva de memoria, la programación de estilo de notificación, confiar el contador de su programa en bibliotecas de abstracción de varias capas (como dijo @Ian), ocultar la lentitud, etc.

Mike Dunlavey
fuente
2

C no tiene ninguna ventaja de rendimiento en comparación con C ++ si hace lo mismo en ambos lenguajes. Puede tomar cualquier código C antiguo escrito por cualquier programador C decente y convertirlo en código C ++ válido y equivalente, que se ejecutará igual de rápido (a menos que tanto usted como su compilador sepan qué hace la palabra clave "restringir" y la usen de manera efectiva, pero la mayoría de la gente no)

C ++ puede tener un rendimiento muy diferente, más lento o más rápido, si (1) usa la biblioteca estándar de C ++ para hacer cosas que se pueden hacer mucho más rápido y más fácil sin usar la biblioteca, o (2) si usa la biblioteca estándar de C ++ hacer las cosas mucho más fácil y más rápido que reimplementando la biblioteca en C.

gnasher729
fuente
1
esto no parece ofrecer nada sustancial sobre lo que se explicó en 6 respuestas anteriores
mosquito
Creo que esta respuesta menciona un punto importante que nadie más ha mencionado. A primera vista, parece que C ++ es un superconjunto de C, por lo que si puede escribir una implementación rápida de C, entonces debería poder escribir una implementación de C ++ que sea equivalente. Sin embargo, C99 admite la palabra clave restrictiva que permite evitar el alias de puntero no deseado. C ++ no tiene tal soporte. La capacidad de evitar el alias de puntero es una característica importante de Fortran que lo hace útil para aplicaciones de alto rendimiento. Espero que también sea posible obtener un mejor rendimiento de C99 que C ++ en dominios similares.
user27539