/* * Scenario 4b: Array vs Linked List Traversal * ============================================ * Arrays have excellent cache locality; linked lists do not. * This demonstrates why "O(n) vs O(n)" can have very different constants. * * Compile: gcc -O2 -o list_vs_array list_vs_array.c */ #include #include #include #define N 10000000 /* 10 million elements */ struct node { int value; struct node *next; }; double get_time(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } /* Sum array elements */ long sum_array(int *arr, int n) { long sum = 0; for (int i = 0; i < n; i++) { sum += arr[i]; } return sum; } /* Sum linked list elements */ long sum_list(struct node *head) { long sum = 0; struct node *curr = head; while (curr != NULL) { sum += curr->value; curr = curr->next; } return sum; } /* Create array */ int *create_array(int n) { int *arr = malloc(n * sizeof(int)); if (!arr) { perror("malloc array"); exit(1); } for (int i = 0; i < n; i++) { arr[i] = i % 100; } return arr; } /* Create linked list - nodes allocated sequentially (best case for list) */ struct node *create_list_sequential(int n) { struct node *nodes = malloc(n * sizeof(struct node)); if (!nodes) { perror("malloc list"); exit(1); } for (int i = 0; i < n - 1; i++) { nodes[i].value = i % 100; nodes[i].next = &nodes[i + 1]; } nodes[n - 1].value = (n - 1) % 100; nodes[n - 1].next = NULL; return nodes; } /* Create linked list - nodes allocated randomly (worst case for cache) */ struct node *create_list_scattered(int n) { /* Allocate nodes individually to scatter them in memory */ struct node **nodes = malloc(n * sizeof(struct node *)); if (!nodes) { perror("malloc"); exit(1); } /* Allocate each node separately */ for (int i = 0; i < n; i++) { nodes[i] = malloc(sizeof(struct node)); if (!nodes[i]) { perror("malloc node"); exit(1); } nodes[i]->value = i % 100; } /* Shuffle the order (Fisher-Yates) */ srand(42); for (int i = n - 1; i > 0; i--) { int j = rand() % (i + 1); struct node *tmp = nodes[i]; nodes[i] = nodes[j]; nodes[j] = tmp; } /* Link them in shuffled order */ for (int i = 0; i < n - 1; i++) { nodes[i]->next = nodes[i + 1]; } nodes[n - 1]->next = NULL; struct node *head = nodes[0]; free(nodes); /* Free the pointer array, not the nodes */ return head; } void free_scattered_list(struct node *head) { while (head != NULL) { struct node *next = head->next; free(head); head = next; } } int main(void) { printf("Comparing array vs linked list traversal (%d elements)\n\n", N); double start, elapsed; long result; /* Array */ printf("Creating array...\n"); int *arr = create_array(N); start = get_time(); result = sum_array(arr, N); elapsed = get_time() - start; printf("Array sum: %ld in %.4f seconds\n", result, elapsed); double array_time = elapsed; free(arr); /* Sequential linked list (best case for list) */ printf("\nCreating sequential linked list...\n"); struct node *list_seq = create_list_sequential(N); start = get_time(); result = sum_list(list_seq); elapsed = get_time() - start; printf("List sum (sequential): %ld in %.4f seconds\n", result, elapsed); double list_seq_time = elapsed; free(list_seq); /* Scattered linked list (worst case for cache) */ printf("\nCreating scattered linked list (this takes a while)...\n"); struct node *list_scat = create_list_scattered(N); start = get_time(); result = sum_list(list_scat); elapsed = get_time() - start; printf("List sum (scattered): %ld in %.4f seconds\n", result, elapsed); double list_scat_time = elapsed; free_scattered_list(list_scat); printf("\n--- Summary ---\n"); printf("Array: %.4fs (baseline)\n", array_time); printf("List (sequential): %.4fs (%.1fx slower)\n", list_seq_time, list_seq_time / array_time); printf("List (scattered): %.4fs (%.1fx slower)\n", list_scat_time, list_scat_time / array_time); printf("\nTo see cache behavior:\n"); printf(" perf stat -e cache-misses,cache-references ./list_vs_array\n"); return 0; }