Μετάβαση στο περιεχόμενο

Λογισμικό & Προγραμματισμός Συστημάτων Υψηλής Επίδοσης — Ιανουάριος 2026

Μεταγραφή από τις φωτογραφίες PXL_20260204_1608281112.jpg και PXL_20260204_1608489962.jpg (δύο σελίδες της ίδιας εξεταστικής κόλλας).

PXL_20260204_1608281112.jpg

Λογισμικό και Προγραμματισμός Συστημάτων Υψηλής Επίδοσης

Εξεταστική Περίοδος: Ιανουαρίου 2026

  1. Ένας αλγόριθμος εκτελείται σε 16 πυρήνες. Ο σειριακός χρόνος είναι 100 s. Το κόστος συγχρονισμού είναι 2 s ανά πυρήνα. Ποιος είναι ο πραγματικός χρόνος εκτέλεσης;

  2. Τι σημαίνει ότι μια παράλληλη εφαρμογή παρουσιάζει ιδανικό weak scaling;

  3. Ένα σύστημα έχει τα εξής χαρακτηριστικά: peak performance: 500 GFLOP/s, memory bandwidth: 100 GB/s Ένας κώδικας εκτελεί 2.5 FLOPs ανά byte δεδομένων. Ποια είναι η ανώτερη θεωρητική απόδοση του κώδικα;

  4. Ένα σύστημα έχει:

  5. 2 sockets \(\times\) 16 πυρήνες
  6. 2.5 GHz
  7. SIMD πλάτους 8 (double)
  8. FMA
  9. Memory bandwidth: 200 GB/s

Ένας kernel εκτελεί 1 FMA ανά 16 bytes δεδομένων. Ποια είναι η ανώτερη θεωρητική απόδοση του kernel;

  1. Ένας OpenMP βρόχος έχει τέλειο load balance αλλά χειρότερη απόδοση από τη σειριακή εκτέλεση. Ποια είναι η πιο πιθανή αιτία;

  2. Ένας κώδικας:

  3. κατά 50% είναι πλήρως παραλληλοποιήσιμος με νήματα και διανυσματικές εντολές
  4. κατά 30% είναι μόνο παραλληλοποιήσιμος με νήματα
  5. κατά 20% είναι σειριακός

Το σύστημα έχει: - 8 πυρήνες - SIMD πλάτους 4 - συχνότητα 2 GHz

Υποθέτουμε ιδανική αξιοποίηση όπου είναι δυνατόν. Ποια είναι η μέγιστη συνολική επιτάχυνση;

  1. Ποια θα είναι η τιμή της μεταβλητής test_var μετά την εκτέλεση της παράλληλης περιοχής στον παρακάτω OpenMP κώδικα;
int test_var = 1;

#pragma omp parallel num_threads(5) reduction(max:test_var)
{
    test_var = omp_get_thread_num() - omp_get_num_threads();
}
  1. Ποιες θα είναι οι τελικές τιμές του διανύσματος A μετά την εκτέλεση του παρακάτω OpenMP κώδικα; Θεωρήστε πως το διάνυσμα A είναι αρχικοποιημένο με μηδενικές τιμές. Εξηγήστε.
void process(double A[13])
{
    omp_set_num_threads(2);
    #pragma omp parallel for schedule(static,1)
    for (int i = 1; i <= 12; i += 3) {
        A[i] = i + omp_get_thread_num();
    }
}
  1. Στον παρακάτω κώδικα, η συνάρτηση initialize θέτει τιμές σε ένα διάνυσμα A διάστασης N χρησιμοποιώντας την thread-safe και χρονοβόρα συνάρτηση work, που είναι υλοποιημένη κάπου αλλού. Γράψτε μία ισοδύναμη υλοποίηση του παράλληλου κώδικα που θα βασίζεται μόνο στην οδηγία parallel.
double A[N];
#pragma omp parallel for schedule(static, 1)
for (int i = 0; i < N; i++) {
    A[i] = work(i);
}
  1. Στον παρακάτω κώδικα, η συνάρτηση initialize θέτει τιμές σε ένα διάνυσμα A διάστασης N χρησιμοποιώντας την thread-safe και χρονοβόρα συνάρτηση work, που είναι υλοποιημένη κάπου αλλού. Γράψτε μία ισοδύναμη υλοποίηση του παράλληλου κώδικα που θα βασίζεται μόνο στην οδηγία parallel.

PXL_20260204_1608489962.jpg

double A[N];
#pragma omp parallel for schedule(static, 2)
for (int i = 0; i < N; i++) {
    A[i] = work(i);
}
  1. Ποια τιμή θα εκτυπωθεί όταν ο παρακάτω κώδικας MPI εκτελεστεί με 4 διεργασίες (mpirun -n 4 ); Εξηγήστε.
int rank, size;
MPI_Comm_rank(MPI_COMM_WORLD, &rank);
MPI_Comm_size(MPI_COMM_WORLD, &size);

int var1 = rank-2;
int var2 = rank;
int var3 = 0;
MPI_Reduce(&var1,&var2,1, MPI_INT, MPI_SUM,0, MPI_COMM_WORLD);
MPI_Allreduce(&var2, &var3, 1, MPI_DOUBLE, MPI_INT, MPI_COMM_WORLD);

if (rank == 1) printf("%f\n", var3);
  1. Στον παρακάτω κώδικα, η συνάρτηση initialize θέτει τιμές σε ένα διάνυσμα A διάστασης N χρησιμοποιώντας την thread-safe και χρονοβόρα συνάρτηση work, που είναι υλοποιημένη κάπου αλλού. Γράψτε μία ισοδύναμη υλοποίηση του παράλληλου κώδικα που θα βασίζεται στο MPI.
double A[N];
#pragma omp parallel for schedule(static, 1)
for (int i = 0; i < N; i++) {
    A[i] = work(i);
}
  1. Τι κάνει το παρακάτω τμήμα κώδικα;
__m256 a = _mm256_load_ps(array1);
__m256 b = _mm256_load_ps(array2);
__m256 c = _mm256_sub_ps(a, b);
  1. Αναφέρετε και διορθώστε πιθανά σφάλματα στον παρακάτω κώδικα.
int nThreads = 8;
double*data;
data = (double *) malloc(32*sizeof(double));
for (int i=0; i<32; ++i) data[i] = drand48();

#pragma omp parallel num_threads(nThreads)
{
    double * const myData = data + 4*omp_get_thread_num();
    __m128 c4 = _mm_set1_ps(M_PI); // = 3.141592...
    __m128 r4 = _mm_load_ps(myData);
    __m128 a4 = _mm_mul_ps(c4, _mm_mul_ps(r4, r4));
    _mm_store_ps(myData, a4);
}
  1. Ποιος είναι ο σκοπός χρήσης της συνάρτησης __syncthreads() σε έναν υπολογιστικό πυρήνα CUDA;

  2. Ποια είναι η έξοδος από την εκτέλεση του παρακάτω κώδικα CUDA;

__global__ void kernel(int *a) {
    int tid = threadIdx.x;
    a[tid] = tid * 2;
}

int main() {
    int h_a[4], *d_a;
    cudaMalloc(&d_a, 4 * sizeof(int));
    kernel<<<2, 2>>>(d_a);
    cudaMemcpy(h_a, d_a, 4*sizeof(int), cudaMemcpyDeviceToHost);
    printf("%d %d %d %d", h_a[0], h_a[1], h_a[2], h_a[3]);
    cudaFree(d_a);
    return 0;
}
  1. Στον παρακάτω κώδικα, η συνάρτηση initialize θέτει τιμές σε ένα διάνυσμα A διάστασης N χρησιμοποιώντας την thread-safe και χρονοβόρα συνάρτηση work, που είναι υλοποιημένη κάπου αλλού. Γράψτε μία ισοδύναμη υλοποίηση του παράλληλου κώδικα που θα βασίζεται στο CUDA.
double A[N];
#pragma omp parallel for schedule(static, 1)
for (int i = 0; i < N; i++) {
    A[i] = work(i);
}

✖ Βρήκα λάθος στη μεταγραφή