Λογισμικό & Προγραμματισμός Συστημάτων Υψηλής Επίδοσης — Ιανουάριος 2026¶
Μεταγραφή από τις φωτογραφίες PXL_20260204_1608281112.jpg και PXL_20260204_1608489962.jpg (δύο σελίδες της ίδιας εξεταστικής κόλλας).
PXL_20260204_1608281112.jpg¶
Λογισμικό και Προγραμματισμός Συστημάτων Υψηλής Επίδοσης
Εξεταστική Περίοδος: Ιανουαρίου 2026
-
Ένας αλγόριθμος εκτελείται σε 16 πυρήνες. Ο σειριακός χρόνος είναι 100 s. Το κόστος συγχρονισμού είναι 2 s ανά πυρήνα. Ποιος είναι ο πραγματικός χρόνος εκτέλεσης;
-
Τι σημαίνει ότι μια παράλληλη εφαρμογή παρουσιάζει ιδανικό weak scaling;
-
Ένα σύστημα έχει τα εξής χαρακτηριστικά: peak performance: 500 GFLOP/s, memory bandwidth: 100 GB/s Ένας κώδικας εκτελεί 2.5 FLOPs ανά byte δεδομένων. Ποια είναι η ανώτερη θεωρητική απόδοση του κώδικα;
-
Ένα σύστημα έχει:
- 2 sockets \(\times\) 16 πυρήνες
- 2.5 GHz
- SIMD πλάτους 8 (double)
- FMA
- Memory bandwidth: 200 GB/s
Ένας kernel εκτελεί 1 FMA ανά 16 bytes δεδομένων. Ποια είναι η ανώτερη θεωρητική απόδοση του kernel;
-
Ένας OpenMP βρόχος έχει τέλειο load balance αλλά χειρότερη απόδοση από τη σειριακή εκτέλεση. Ποια είναι η πιο πιθανή αιτία;
-
Ένας κώδικας:
- κατά 50% είναι πλήρως παραλληλοποιήσιμος με νήματα και διανυσματικές εντολές
- κατά 30% είναι μόνο παραλληλοποιήσιμος με νήματα
- κατά 20% είναι σειριακός
Το σύστημα έχει: - 8 πυρήνες - SIMD πλάτους 4 - συχνότητα 2 GHz
Υποθέτουμε ιδανική αξιοποίηση όπου είναι δυνατόν. Ποια είναι η μέγιστη συνολική επιτάχυνση;
- Ποια θα είναι η τιμή της μεταβλητής 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();
}
- Ποιες θα είναι οι τελικές τιμές του διανύσματος 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();
}
}
- Στον παρακάτω κώδικα, η συνάρτηση 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);
}
- Στον παρακάτω κώδικα, η συνάρτηση 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);
}
- Ποια τιμή θα εκτυπωθεί όταν ο παρακάτω κώδικας 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);
- Στον παρακάτω κώδικα, η συνάρτηση 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);
}
- Τι κάνει το παρακάτω τμήμα κώδικα;
__m256 a = _mm256_load_ps(array1);
__m256 b = _mm256_load_ps(array2);
__m256 c = _mm256_sub_ps(a, b);
- Αναφέρετε και διορθώστε πιθανά σφάλματα στον παρακάτω κώδικα.
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);
}
-
Ποιος είναι ο σκοπός χρήσης της συνάρτησης __syncthreads() σε έναν υπολογιστικό πυρήνα CUDA;
-
Ποια είναι η έξοδος από την εκτέλεση του παρακάτω κώδικα 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;
}
- Στον παρακάτω κώδικα, η συνάρτηση 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);
}