Heim >Backend-Entwicklung >C++ >Das Streben nach Leistung Teil III: C Force

Das Streben nach Leistung Teil III: C Force

王林
王林Original
2024-08-06 01:10:021230Durchsuche

The Quest for Performance Part III : C Force

In den beiden vorherigen Teilen dieser Serie haben wir uns mit der Leistung von Floating-Operationen in Perl befasst,
Python und R in einem Spielzeugbeispiel, das die Funktion cos(sin(sqrt(x))) berechnet hat, wobei x ein sehr großes Array von 50 Millionen Gleitkommazahlen mit doppelter Genauigkeit war.
Hybridimplementierungen, die den rechenintensiven Teil an C delegierten, gehörten zu den leistungsstärksten Implementierungen. In diesem Teil schweifen wir etwas ab und betrachten die Leistung einer reinen C-Code-Implementierung des Spielzeugbeispiels.
Der C-Code liefert weitere Einblicke in die Bedeutung der Speicherlokalität für die Leistung (standardmäßig werden Elemente in einem C-Array an sequentiellen Adressen im Speicher gespeichert, und numerische APIs wie PDL oder Numpy-Schnittstelle mit solchen Containern) im Vergleich zu Containern ,
z.B. Perl-Arrays, die ihre Werte nicht in sequentiellen Adressen im Speicher speichern. Zu guter Letzt ermöglichen uns die C-Code-Implementierungen zu beurteilen, ob Flags im Zusammenhang mit Gleitkommaoperationen für den Low-Level-Compiler (in diesem Fall gcc) die Leistung beeinträchtigen können.
Dieser Punkt ist hervorzuheben: Normalsterbliche sind vollständig von der Wahl der Compiler-Flags abhängig, wenn sie ihre „Installation“ über die Pipeline weiterleiten oder ihre Inline-Datei erstellen. Wenn man diese Flaggen nicht berührt, ist man sich glücklicherweise nicht bewusst, was sie möglicherweise übersehen oder welche Fallstricke sie vermeiden.
Das bescheidene C-Datei-Makefile ermöglicht es, solche Leistungsbewertungen explizit vorzunehmen.

Der C-Code für unser Spielzeugbeispiel ist unten vollständig aufgeführt. Der Code ist ziemlich selbsterklärend, daher werde ich keine Zeit mit Erklärungen verschwenden, außer darauf hinzuweisen, dass er vier Funktionen für

enthält
  • Nicht sequentielle Berechnung der teuren Funktion: Alle drei Gleitkommaoperationen finden innerhalb einer einzigen Schleife mit einem Thread statt
  • Sequentielle Berechnungen der teuren Funktion: Jede der drei Gleitkomma-Funktionsauswertungen erfolgt innerhalb einer separaten Schleife mit einem Thread
  • Nicht-sequentieller OpenMP-Code: Thread-Version des nicht-sequentiellen Codes
  • Sequentieller OpenMP-Code: Thread des sequentiellen Codes

In diesem Fall kann man hoffen, dass der Compiler intelligent genug ist, um zu erkennen, dass die Quadratwurzel auf gepackte (vektorisierte) Gleitkommaoperationen in Assembler abgebildet wird, sodass eine Funktion mithilfe der entsprechenden SIMD-Anweisungen vektorisiert werden kann (beachten Sie, dass wir dies getan haben). Verwenden Sie nicht das simd-Programm für die OpenMP-Codes).
Möglicherweise kann die Beschleunigung durch die Vektorisierung den Leistungsverlust ausgleichen, der durch den wiederholten Zugriff auf dieselben Speicherorte entsteht (oder auch nicht).

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <omp.h>

// simulates a large array of random numbers
double*  simulate_array(int num_of_elements,int seed);
// OMP environment functions
void _set_openmp_schedule_from_env();
void _set_num_threads_from_env();



// functions to modify C arrays 
void map_c_array(double* array, int len);
void map_c_array_sequential(double* array, int len);
void map_C_array_using_OMP(double* array, int len);
void map_C_array_sequential_using_OMP(double* array, int len);

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <array_size>\n", argv[0]);
        return 1;
    }

    int array_size = atoi(argv[1]);
    // printf the array size
    printf("Array size: %d\n", array_size);
    double *array = simulate_array(array_size, 1234);

    // Set OMP environment
    _set_openmp_schedule_from_env();
    _set_num_threads_from_env();

    // Perform calculations and collect timing data
    double start_time, end_time, elapsed_time;
    // Non-Sequential calculation
    start_time = omp_get_wtime();
    map_c_array(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Non-sequential calculation time: %f seconds\n", elapsed_time);
    free(array);

    // Sequential calculation
    array = simulate_array(array_size, 1234);
    start_time = omp_get_wtime();
    map_c_array_sequential(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Sequential calculation time: %f seconds\n", elapsed_time);
    free(array);

    array = simulate_array(array_size, 1234);
    // Parallel calculation using OMP
    start_time = omp_get_wtime();
    map_C_array_using_OMP(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Parallel calculation using OMP time: %f seconds\n", elapsed_time);
    free(array);

    // Sequential calculation using OMP
    array = simulate_array(array_size, 1234);
    start_time = omp_get_wtime();
    map_C_array_sequential_using_OMP(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Sequential calculation using OMP time: %f seconds\n", elapsed_time);

    free(array);
    return 0;
}



/*
*******************************************************************************
* OMP environment functions
*******************************************************************************
*/
void _set_openmp_schedule_from_env() {
  char *schedule_env = getenv("OMP_SCHEDULE");
  printf("Schedule from env %s\n", getenv("OMP_SCHEDULE"));
  if (schedule_env != NULL) {
    char *kind_str = strtok(schedule_env, ",");
    char *chunk_size_str = strtok(NULL, ",");

    omp_sched_t kind;
    if (strcmp(kind_str, "static") == 0) {
      kind = omp_sched_static;
    } else if (strcmp(kind_str, "dynamic") == 0) {
      kind = omp_sched_dynamic;
    } else if (strcmp(kind_str, "guided") == 0) {
      kind = omp_sched_guided;
    } else {
      kind = omp_sched_auto;
    }
    int chunk_size = atoi(chunk_size_str);
    omp_set_schedule(kind, chunk_size);
  }
}

void _set_num_threads_from_env() {
  char *num = getenv("OMP_NUM_THREADS");
  printf("Number of threads = %s from within C\n", num);
  omp_set_num_threads(atoi(num));
}
/*
*******************************************************************************
* Functions that modify C arrays whose address is passed from Perl in C
*******************************************************************************
*/

double*  simulate_array(int num_of_elements, int seed) {
  srand(seed); // Seed the random number generator
  double *array = (double *)malloc(num_of_elements * sizeof(double));
  for (int i = 0; i < num_of_elements; i++) {
    array[i] =
        (double)rand() / RAND_MAX; // Generate a random double between 0 and 1
  }
  return array;
}

void map_c_array(double *array, int len) {
  for (int i = 0; i < len; i++) {
    array[i] = cos(sin(sqrt(array[i])));
  }
}

void map_c_array_sequential(double* array, int len) {
  for (int i = 0; i < len; i++) {
    array[i] = sqrt(array[i]);
  }
  for (int i = 0; i < len; i++) {
    array[i] = sin(array[i]);
  }
  for (int i = 0; i < len; i++) {
    array[i] = cos(array[i]);
  }
}

void map_C_array_using_OMP(double* array, int len) {
#pragma omp parallel
  {
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = cos(sin(sqrt(array[i])));
    }
  }
}

void map_C_array_sequential_using_OMP(double* array, int len) {
#pragma omp parallel
  {
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = sqrt(array[i]);
    }
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = sin(array[i]);
    }
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = cos(array[i]);
    }
  }
}

Eine entscheidende Frage ist, ob die Verwendung schneller Floating-Compiler-Flags, ein Trick, der Geschwindigkeit gegen Genauigkeit des Codes tauscht, die Leistung beeinträchtigen kann.
Hier ist das Makefile ohne dieses Compiler-Flag

CC = gcc
CFLAGS = -O3 -ftree-vectorize  -march=native  -Wall -std=gnu11 -fopenmp -fstrict-aliasing 
LDFLAGS = -fPIE -fopenmp
LIBS =  -lm

SOURCES = inplace_array_mod_with_OpenMP.c
OBJECTS = $(SOURCES:.c=_noffmath_gcc.o)
EXECUTABLE = inplace_array_mod_with_OpenMP_noffmath_gcc

all: $(SOURCES) $(EXECUTABLE)

clean:
    rm -f $(OBJECTS) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@

%_noffmath_gcc.o : %.c 
    $(CC) $(CFLAGS) -c $< -o $@

und hier ist das mit dieser Flagge:

CC = gcc
CFLAGS = -O3 -ftree-vectorize  -march=native -Wall -std=gnu11 -fopenmp -fstrict-aliasing -ffast-math
LDFLAGS = -fPIE -fopenmp
LIBS =  -lm

SOURCES = inplace_array_mod_with_OpenMP.c
OBJECTS = $(SOURCES:.c=_gcc.o)
EXECUTABLE = inplace_array_mod_with_OpenMP_gcc

all: $(SOURCES) $(EXECUTABLE)

clean:
    rm -f $(OBJECTS) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@

%_gcc.o : %.c 
    $(CC) $(CFLAGS) -c $< -o $@

Und hier sind die Ergebnisse der Ausführung dieser beiden Programme

  • Ohne -ffast-math
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_noffmath_gcc 50000000
Array size: 50000000
Schedule from env guided,1
Number of threads = 8 from within C
Non-sequential calculation time: 1.12 seconds
Sequential calculation time: 0.95 seconds
Parallel calculation using OMP time: 0.17 seconds
Sequential calculation using OMP time: 0.15 seconds
  • Mit -ffast-math
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_gcc 50000000
Array size: 50000000
Schedule from env guided,1
Number of threads = 8 from within C
Non-sequential calculation time: 0.27 seconds
Sequential calculation time: 0.28 seconds
Parallel calculation using OMP time: 0.05 seconds
Sequential calculation using OMP time: 0.06 seconds

Beachten Sie, dass man Fastmath im Numba-Code wie folgt verwenden kann (die Standardeinstellung ist fastmath=False):

@njit(nogil=True,fastmath=True)
def compute_inplace_with_numba(array):
    np.sqrt(array,array)
    np.sin(array,array)
    np.cos(array,array)

Ein paar Punkte, die es zu beachten gilt:

  • Das -ffast-math steigert die Leistung erheblich (ca. 300 % sowohl für den Single-Threaded- als auch den Multi-Threaded-Code), kann aber zu fehlerhaften Ergebnissen führen
  • Fastmath funktioniert auch in Numba, sollte aber aus den gleichen Gründen vermieden werden wie in jeder Anwendung, die Genauigkeit anstrebt
  • Der sequentielle C-Single-Threaded-Code bietet eine ähnliche Leistung wie die Single-Threaded-PDL und Numpy
  • Etwas überraschend ist, dass der sequentielle Code etwa 20 % schneller ist als der nicht-sequentielle Code, wenn die richtige (nicht schnelle) Mathematik verwendet wird.
  • Es überrascht nicht, dass Multithread-Code schneller ist als Single-Threaded-Code :)
  • Ich kann immer noch nicht erklären, wie numbas eine 50-prozentige Leistungssteigerung gegenüber dem C-Code dieser eher einfachen Funktion bietet.

Titel: „The Quest for Performance Part III: C Force“

Datum: 07.07.2024

In den beiden vorherigen Teilen dieser Serie haben wir uns mit der Leistung von Floating-Operationen in Perl befasst,
Python und R in einem Spielzeugbeispiel, das die Funktion cos(sin(sqrt(x))) berechnet hat, wobei x ein sehr großes Array von 50 Millionen Gleitkommazahlen mit doppelter Genauigkeit war.
Hybridimplementierungen, die den rechenintensiven Teil an C delegierten, gehörten zu den leistungsstärksten Implementierungen. In diesem Teil schweifen wir etwas ab und betrachten die Leistung einer reinen C-Code-Implementierung des Spielzeugbeispiels.
Der C-Code liefert weitere Einblicke in die Bedeutung der Speicherlokalität für die Leistung (standardmäßig werden Elemente in einem C-Array an sequentiellen Adressen im Speicher gespeichert, und numerische APIs wie PDL oder Numpy-Schnittstelle mit solchen Containern) im Vergleich zu Containern ,
z.B. Perl-Arrays, die ihre Werte nicht in sequentiellen Adressen im Speicher speichern. Zu guter Letzt ermöglichen uns die C-Code-Implementierungen zu beurteilen, ob Flags im Zusammenhang mit Gleitkommaoperationen für den Low-Level-Compiler (in diesem Fall gcc) die Leistung beeinträchtigen können.
Dieser Punkt ist hervorzuheben: Normalsterbliche sind vollständig von der Wahl der Compiler-Flags abhängig, wenn sie ihre „Installation“ über die Pipeline weiterleiten oder ihre Inline-Datei erstellen. Wenn man diese Flaggen nicht berührt, ist man sich glücklicherweise nicht bewusst, was sie möglicherweise übersehen oder welche Fallstricke sie vermeiden.
Das bescheidene C-Datei-Makefile ermöglicht es, solche Leistungsbewertungen explizit durchzuführen.

Der C-Code für unser Spielzeugbeispiel ist unten vollständig aufgeführt. Der Code ist ziemlich selbsterklärend, daher werde ich keine Zeit mit Erklärungen verschwenden, außer darauf hinzuweisen, dass er vier Funktionen für

enthält
  • Nicht sequentielle Berechnung der teuren Funktion: Alle drei Gleitkommaoperationen finden innerhalb einer einzigen Schleife mit einem Thread statt
  • Sequentielle Berechnungen der teuren Funktion: Jede der drei Gleitkomma-Funktionsauswertungen erfolgt innerhalb einer separaten Schleife mit einem Thread
  • Nicht-sequentieller OpenMP-Code: Thread-Version des nicht-sequentiellen Codes
  • Sequentieller OpenMP-Code: Thread des sequentiellen Codes

In diesem Fall kann man hoffen, dass der Compiler intelligent genug ist, um zu erkennen, dass die Quadratwurzel auf gepackte (vektorisierte) Gleitkommaoperationen in Assembler abgebildet wird, sodass eine Funktion mithilfe der entsprechenden SIMD-Anweisungen vektorisiert werden kann (beachten Sie, dass wir dies getan haben). Verwenden Sie nicht das simd-Programm für die OpenMP-Codes).
Möglicherweise kann die Beschleunigung durch die Vektorisierung den Leistungsverlust ausgleichen, der durch den wiederholten Zugriff auf dieselben Speicherorte entsteht (oder auch nicht).

#include <stdlib.h>
#include <string.h>
#include <math.h>
#include <stdio.h>
#include <omp.h>

// simulates a large array of random numbers
double*  simulate_array(int num_of_elements,int seed);
// OMP environment functions
void _set_openmp_schedule_from_env();
void _set_num_threads_from_env();



// functions to modify C arrays 
void map_c_array(double* array, int len);
void map_c_array_sequential(double* array, int len);
void map_C_array_using_OMP(double* array, int len);
void map_C_array_sequential_using_OMP(double* array, int len);

int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: %s <array_size>\n", argv[0]);
        return 1;
    }

    int array_size = atoi(argv[1]);
    // printf the array size
    printf("Array size: %d\n", array_size);
    double *array = simulate_array(array_size, 1234);

    // Set OMP environment
    _set_openmp_schedule_from_env();
    _set_num_threads_from_env();

    // Perform calculations and collect timing data
    double start_time, end_time, elapsed_time;
    // Non-Sequential calculation
    start_time = omp_get_wtime();
    map_c_array(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Non-sequential calculation time: %f seconds\n", elapsed_time);
    free(array);

    // Sequential calculation
    array = simulate_array(array_size, 1234);
    start_time = omp_get_wtime();
    map_c_array_sequential(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Sequential calculation time: %f seconds\n", elapsed_time);
    free(array);

    array = simulate_array(array_size, 1234);
    // Parallel calculation using OMP
    start_time = omp_get_wtime();
    map_C_array_using_OMP(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Parallel calculation using OMP time: %f seconds\n", elapsed_time);
    free(array);

    // Sequential calculation using OMP
    array = simulate_array(array_size, 1234);
    start_time = omp_get_wtime();
    map_C_array_sequential_using_OMP(array, array_size);
    end_time = omp_get_wtime();
    elapsed_time = end_time - start_time;
    printf("Sequential calculation using OMP time: %f seconds\n", elapsed_time);

    free(array);
    return 0;
}



/*
*******************************************************************************
* OMP environment functions
*******************************************************************************
*/
void _set_openmp_schedule_from_env() {
  char *schedule_env = getenv("OMP_SCHEDULE");
  printf("Schedule from env %s\n", getenv("OMP_SCHEDULE"));
  if (schedule_env != NULL) {
    char *kind_str = strtok(schedule_env, ",");
    char *chunk_size_str = strtok(NULL, ",");

    omp_sched_t kind;
    if (strcmp(kind_str, "static") == 0) {
      kind = omp_sched_static;
    } else if (strcmp(kind_str, "dynamic") == 0) {
      kind = omp_sched_dynamic;
    } else if (strcmp(kind_str, "guided") == 0) {
      kind = omp_sched_guided;
    } else {
      kind = omp_sched_auto;
    }
    int chunk_size = atoi(chunk_size_str);
    omp_set_schedule(kind, chunk_size);
  }
}

void _set_num_threads_from_env() {
  char *num = getenv("OMP_NUM_THREADS");
  printf("Number of threads = %s from within C\n", num);
  omp_set_num_threads(atoi(num));
}
/*
*******************************************************************************
* Functions that modify C arrays whose address is passed from Perl in C
*******************************************************************************
*/

double*  simulate_array(int num_of_elements, int seed) {
  srand(seed); // Seed the random number generator
  double *array = (double *)malloc(num_of_elements * sizeof(double));
  for (int i = 0; i < num_of_elements; i++) {
    array[i] =
        (double)rand() / RAND_MAX; // Generate a random double between 0 and 1
  }
  return array;
}

void map_c_array(double *array, int len) {
  for (int i = 0; i < len; i++) {
    array[i] = cos(sin(sqrt(array[i])));
  }
}

void map_c_array_sequential(double* array, int len) {
  for (int i = 0; i < len; i++) {
    array[i] = sqrt(array[i]);
  }
  for (int i = 0; i < len; i++) {
    array[i] = sin(array[i]);
  }
  for (int i = 0; i < len; i++) {
    array[i] = cos(array[i]);
  }
}

void map_C_array_using_OMP(double* array, int len) {
#pragma omp parallel
  {
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = cos(sin(sqrt(array[i])));
    }
  }
}

void map_C_array_sequential_using_OMP(double* array, int len) {
#pragma omp parallel
  {
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = sqrt(array[i]);
    }
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = sin(array[i]);
    }
#pragma omp for schedule(runtime) nowait
    for (int i = 0; i < len; i++) {
      array[i] = cos(array[i]);
    }
  }
}

Eine entscheidende Frage ist, ob die Verwendung schneller Floating-Compiler-Flags, ein Trick, der Geschwindigkeit gegen Genauigkeit des Codes tauscht, die Leistung beeinträchtigen kann.
Hier ist das Makefile ohne dieses Compiler-Flag

CC = gcc
CFLAGS = -O3 -ftree-vectorize  -march=native  -Wall -std=gnu11 -fopenmp -fstrict-aliasing 
LDFLAGS = -fPIE -fopenmp
LIBS =  -lm

SOURCES = inplace_array_mod_with_OpenMP.c
OBJECTS = $(SOURCES:.c=_noffmath_gcc.o)
EXECUTABLE = inplace_array_mod_with_OpenMP_noffmath_gcc

all: $(SOURCES) $(EXECUTABLE)

clean:
    rm -f $(OBJECTS) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@

%_noffmath_gcc.o : %.c 
    $(CC) $(CFLAGS) -c $< -o $@

und hier ist das mit dieser Flagge:

CC = gcc
CFLAGS = -O3 -ftree-vectorize  -march=native -Wall -std=gnu11 -fopenmp -fstrict-aliasing -ffast-math
LDFLAGS = -fPIE -fopenmp
LIBS =  -lm

SOURCES = inplace_array_mod_with_OpenMP.c
OBJECTS = $(SOURCES:.c=_gcc.o)
EXECUTABLE = inplace_array_mod_with_OpenMP_gcc

all: $(SOURCES) $(EXECUTABLE)

clean:
    rm -f $(OBJECTS) $(EXECUTABLE)

$(EXECUTABLE): $(OBJECTS)
    $(CC) $(LDFLAGS) $(OBJECTS) $(LIBS) -o $@

%_gcc.o : %.c 
    $(CC) $(CFLAGS) -c $< -o $@

Und hier sind die Ergebnisse der Ausführung dieser beiden Programme

  • Ohne -ffast-math
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_noffmath_gcc 50000000
Array size: 50000000
Schedule from env guided,1
Number of threads = 8 from within C
Non-sequential calculation time: 1.12 seconds
Sequential calculation time: 0.95 seconds
Parallel calculation using OMP time: 0.17 seconds
Sequential calculation using OMP time: 0.15 seconds
  • Mit -ffast-math
OMP_SCHEDULE=guided,1 OMP_NUM_THREADS=8 ./inplace_array_mod_with_OpenMP_gcc 50000000
Array size: 50000000
Schedule from env guided,1
Number of threads = 8 from within C
Non-sequential calculation time: 0.27 seconds
Sequential calculation time: 0.28 seconds
Parallel calculation using OMP time: 0.05 seconds
Sequential calculation using OMP time: 0.06 seconds

Beachten Sie, dass man Fastmath im Numba-Code wie folgt verwenden kann (der Standardwert ist fastmath=False):

@njit(nogil=True,fastmath=True)
def compute_inplace_with_numba(array):
    np.sqrt(array,array)
    np.sin(array,array)
    np.cos(array,array)

Ein paar Punkte, die es zu beachten gilt:

  • Das -ffast-math steigert die Leistung erheblich (ca. 300 % sowohl für den Single-Threaded- als auch den Multi-Threaded-Code), kann aber zu fehlerhaften Ergebnissen führen
  • Fastmath funktioniert auch in Numba, sollte aber aus den gleichen Gründen vermieden werden wie in jeder Anwendung, die Genauigkeit anstrebt
  • Der sequentielle C-Single-Threaded-Code bietet eine ähnliche Leistung wie die Single-Threaded-PDL und Numpy
  • Etwas überraschend ist, dass der sequentielle Code etwa 20 % schneller ist als der nicht-sequentielle Code, wenn die richtige (nicht schnelle) Mathematik verwendet wird.
  • Es überrascht nicht, dass Multithread-Code schneller ist als Single-Threaded-Code :)
  • Ich kann immer noch nicht erklären, wie numbas eine 50-prozentige Leistungssteigerung gegenüber dem C-Code dieser eher einfachen Funktion bietet.

Das obige ist der detaillierte Inhalt vonDas Streben nach Leistung Teil III: C Force. Für weitere Informationen folgen Sie bitte anderen verwandten Artikeln auf der PHP chinesischen Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn