es.davy.ai

Preguntas y respuestas de programación confiables

¿Tienes una pregunta?

Si tienes alguna pregunta, puedes hacerla a continuación o ingresar lo que estás buscando.

¿Por qué es tan más lento en contexto que cuando está aislado?

Al ejecutar el programa de ejemplo adjunto, parece que la función tan es aproximadamente dos veces más lenta en contexto en comparación con cuando está aislada. Este es el resultado en mi máquina:

justtan(): ~16.062430 ns/iter
notan():   ~30.852820 ns/iter
withtan(): ~60.703100 ns/iter
empty():   ~0.355270 ns/iter

Esperaría que withtan() sea de ~45 ns o menos, dado que es una combinación de justtan y notan.

Estoy ejecutando macOS 11.5.2 con una CPU Intel i7-4980HQ. Mi versión de cc --version es Apple clang version 13.0.0 (clang-1300.0.29.3). He verificado para asegurarme de que el desensamblaje para withtan y notan es idéntico excepto por la llamada a tan, y que clang está autovectorizando los bucles con instrucciones VEX. También he verificado a través de un depurador que la versión de tan que se llama en tiempo de ejecución también utiliza instrucciones VEX para evitar la penalización de transición SSE-AVX2.

Compilé y ejecuté el programa en una VM de Linux y obtuve un resultado similar (en el depurador, tan también utiliza AVX/VEX). Además, lo pasé por cachegrind y descubrí que esencialmente no hay fallos de caché L1 (0,00%) para ninguna de las funciones, sin embargo, al ejecutarlo a través de cachegrind, todos los tiempos se suman correctamente.

Así es como estoy ejecutando el ejecutable:

cc -Wall -O3 -mavx2 -o main main.c && ./main

Aquí está main.c:


#include <stdint.h> #include <stdio.h> #include <time.h> #include <math.h> // --------------------------------------------------------------------- // -------------------- benchmarking harness --------------------------- int64_t ITERS = 100000000; double black_box(double x) { asm("" : : "r"(&x) : "memory"); return x; } uint64_t nanosec() { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec * 1000000000ull + ts.tv_nsec; } double bench(double (*f)()) { // Warmup for (int i = 0; i < ITERS / 10; i++) { black_box(f()); } uint64_t start = nanosec(); for (int i = 0; i < ITERS; i++) { black_box(f()); } uint64_t end = nanosec(); return (double)(end - start) / (double)ITERS; } // -------------------- end benchmarking harness ----------------------- // --------------------------------------------------------------------- #define LEN 32 #define SUM_LEN 24 double VALS[LEN] = { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 }; __attribute__ ((noinline)) double sum24(double* ptr) { double sum = 0.; for (int i = 0; i < 24; i++) { sum += ptr[i]; } return sum; } __attribute__ ((noinline)) double withtan() { double a = sum24(VALS); double b = sum24(VALS + 1); double c = sum24(VALS + 2); double d = sum24(VALS + 3); return tan(a + b + c + d); } __attribute__ ((noinline)) double notan() { double a = sum24(VALS); double b = sum24(VALS + 1); double c = sum24(VALS + 2); double d = sum24(VALS + 3); return a + b + c + d; } __attribute__ ((noinline)) double justtan() { return tan(black_box(96)); } __attribute__ ((noinline)) double empty() { return 1.; } int main() { printf("justtan(): ~%f ns/iter\n", bench(justtan)); printf("notan(): ~%f ns/iter\n", bench(notan)); printf("withtan(): ~%f ns/iter\n", bench(withtan)); printf("empty(): ~%f ns/iter\n", bench(empty)); }

¿Por qué tan es más lento en contexto que cuando está aislado?</math.h></time.h></stdio.h></stdint.h>

Tags:  , , , ,

Answer

  1. Avatar for davy.ai

    Basándonos en la información proporcionada, no está del todo claro por qué tan parece ser dos veces más lento en contexto en comparación con cuando está aislado. Sin embargo, algunas posibles razones podrían ser:

    1. Combinación de llamadas de función: withtan() combina el cálculo de sum24() con el cálculo de tan(). Es posible que la combinación de estos dos cálculos esté causando algún tipo de interferencia o problema de sincronización, lo que resulte en un rendimiento más lento. Esto podría ser investigado aún más mediante la realización de pruebas de perfilado del código o examinando la salida de ensamblado.
    2. Patrones de acceso a memoria: Es posible que los patrones de acceso a memoria en withtan() estén causando algunos problemas de caché o de acceso a memoria que ralenticen la función. Por ejemplo, si la memoria a la que acceden sum24() y tan() no está contigua o alineada en la memoria, esto podría llevar a errores de caché y ralentizar el cálculo. Esto podría ser investigado aún más mediante el uso de herramientas de profilado de rendimiento como cachegrind o perf.

    3. Optimizaciones del compilador: También es posible que las optimizaciones del compilador no sean capaces de optimizar la combinación de sum24() y tan() tan bien como cuando están separados. Por ejemplo, el compilador podría no ser capaz de vectorizar el bucle en withtan() tan efectivamente como puede vectorizar los bucles en justtan() y notan(). Esto podría ser investigado aún más examinando la salida de ensamblado y comparando el código generado para las diferentes funciones.

    En general, sin más información o contexto, es difícil determinar la causa exacta del rendimiento más lento de tan() en contexto en comparación con cuando está aislado. Una investigación más detallada utilizando herramientas de perfilado de rendimiento y examinando la salida de ensamblado podría proporcionar más información sobre el problema de rendimiento.

Comments are closed.