sinatras/pmpp-eval huggingface dataset:
Question: In a tiled 2D convolution setup where the output tile is TxT and the halo radius is r, the block loads a (T+2r)x(T+2r) input tile into shared memory and allocates a shared-memory array to hold this input tile. How much shared memory is needed per block (in bytes) for single-precision floats?
A. T^2 × 4
B. (T+2r)^2 × 4
C. (T+2r) × 4
D. 0
Answer: B
Explanation: The shared tile size is (T+2r)x(T+2r) floats; at 4 bytes/float -> (T+2r)^2 x 4 bytes.
Question: Consider a 2D convolution implementation that does NOT allocate any shared-memory input tile. Each thread block contains TxT threads, and each thread computes exactly one output element of a TxT output tile. All input reads are served directly from global memory (relying only on hardware caches). For an NxN output, how many thread blocks are required?
A. ceil(N/T) × ceil(N/T)
B. (N/T) × (N/T) (truncate)
C. N × N
D. ceil(N/(T+2r)) × ceil(N/(T+2r))
Answer: A
Explanation: Each block covers a TxT region of the output, so the grid needs ceil(N/T) blocks along each dimension.
Question: Consider a 2D convolution implementation that does NOT allocate any shared-memory input tile. Each thread block contains TxT threads, and each thread computes exactly one output element of a TxT output tile. All input reads are served directly from global memory (relying only on hardware caches). How many threads are launched per block?
A. T^2
B. (T+2r)^2
C. 2T(T+r)
D. N^2
Answer: A
Explanation: One thread per output element over a TxT tile yields TxT = T^2 threads per block.
Question: Consider a 2D convolution implementation that does NOT allocate any shared-memory input tile. Each thread block contains TxT threads, and each thread computes exactly one output element of a TxT output tile. All input reads are served directly from global memory (relying only on hardware caches). How much shared memory is needed per block (in bytes) to hold the input tile when using single-precision floats?
A. (T+2r)^2 × 4
B. T^2 × 4
C. 0
D. 2(T+2r)^2 × 4
Answer: C
Explanation: By definition, this variant allocates no shared-memory input tile; it relies solely on hardware caches.
Question: A 3D seven-point stencil is applied on a cubic grid of size 120x120x120 (including boundary cells). The kernel only writes interior points (i=1..118, j=1..118, k=1..118). How many output grid points are computed per sweep?
Answer: 1643032
Explanation: Interior count = (120-2)^3 = 118^3 = 1,643,032.
Question: A basic 3D stencil kernel launches one thread per grid point over a 120x120x120 domain using blocks of size 8x8x8 threads (no overhang trimming). Using ceil division per dimension, how many thread blocks are launched in total?
Answer: 3375
Explanation: Blocks per dim = ceil(120/8) = 15 -> total blocks = 15^3 = 3,375.
Question: A shared-memory tiled 3D stencil uses IN_TILE_DIM = 8 and a radius r = 1, so OUT_TILE_DIM = IN_TILE_DIM - 2r = 6. Over a 120x120x120 domain, blocks are placed per OUT_TILE_DIM using ceil division per dimension. How many thread blocks are launched in total?
Answer: 8000
Explanation: Blocks per dim = ceil(120/6) = 20 -> total blocks = 20^3 = 8,000.
Question: A coarsened/tiled 3D stencil uses 2D thread blocks of IN_TILE_DIMxIN_TILE_DIM = 32x32 (z handled by coarsening) with radius r = 1, so OUT_TILE_DIM = 30. Over a 120x120x120 domain, blocks are placed on a 3D grid using ceil division by OUT_TILE_DIM in each dimension. How many thread blocks are launched in total?
Answer: 64
Explanation: Blocks per dim = ceil(120/30) = 4 -> total blocks = 4^3 = 64.
Question: A seven-point 3D stencil uses thread blocks of size IN_TILE_DIMxIN_TILE_DIM = 32x32 (z handled by coarsening). The block processes Z_COARSENING = 16 consecutive output z-planes. With radius r = 1, the block must load halo planes before the first and after the last output plane. How many input elements does a single block load over its lifetime? Assume each loaded plane is 32x32 elements.
Answer: 18432
Explanation: Planes loaded = (16 output) + 2 halo = 18 planes; per plane 32x32=1024 -> 18x1024 = 18,432 elements.
Question: Using IN_TILE_DIM=32 and radius r=1 (so OUT_TILE_DIM = 30) with Z_COARSENING = 16, how many output elements does a single block compute over its lifetime? Each output z-plane contributes OUT_TILE_DIM x OUT_TILE_DIM elements.
Answer: 14400
Explanation: Per plane: 30x30 = 900 outputs; over 16 planes: 900x16 = 14,400 outputs.
Question: For a 3D seven-point stencil with IN_TILE_DIM = 32 and radius r = 1 (so OUT_TILE_DIM = 30), and Z_COARSENING = 16, a block loads 18,432 input elements and computes 14,400 output elements. Assume 32-bit floats (4 bytes) and that each output performs 13 FLOPs (7 multiplies + 6 adds). What is the OP/B ratio for reads only? Do not include units; provide a decimal number.
Answer: 2.5390625
Explanation: FLOPs = 14,400x13 = 187,200. Bytes read = 18,432x4 = 73,728. OP/B = 187,200 / 73,728 ~ 2.5390625.
Question: With IN_TILE_DIM=32 and radius r=1, a coarsened 3D stencil stores three 32x32 tiles in shared memory at once (previous, current, next z-plane). Using 32-bit floats (4 bytes), how much shared memory (bytes) does a block need if register tiling is NOT used?
Answer: 12288
Explanation: 3 tiles x (32x32 elements) x 4 B = 3x1024x4 = 12,288 bytes.
Question: With IN_TILE_DIM=32 and radius r=1, if register tiling is used so that only one 32x32 tile is kept in shared memory at a time, how much shared memory (bytes) does a block need? Assume 32-bit floats (4 bytes).
Answer: 4096
Explanation: 1 tile x (32x32 elements) x 4 B = 1024x4 = 4,096 bytes.
Question: Assume each atomic operation to a single global memory variable (so updates serialize) has a total latency of 100 ns. What is the maximum throughput for these atomic operations in operations per second (ops/s)?
Answer: 10000000
Explanation: Throughput = 1 / (100 ns) = 1 / (100x10^-9 s) = 10,000,000 ops/s.
Question: A GPU supports atomic operations in L2 cache. Each atomic takes 4 ns if it hits in L2 and 100 ns if it goes to DRAM. If 90% of atomic operations hit in L2 and all updates target the same global variable (thus serialize), what is the approximate atomic throughput in ops/s?
Answer: 73529412
Explanation: Average latency = 0.9x4 ns + 0.1x100 ns = 13.6 ns. Throughput ~ 1 / 13.6 ns ~ 73,529,412 ops/s (rounded).
Question: A kernel performs 5 floating-point operations per atomic operation. Each atomic operation to a single global variable has latency 100 ns (so atomics serialize). What is the maximum floating-point throughput (in FLOP/s) limited by the atomic throughput?
Answer: 50000000
Explanation: Atomic throughput = 1/(100 ns) = 10,000,000 ops/s. FLOP/s = 10,000,000 x 5 = 50,000,000.
Question: A kernel replaces global atomics with shared-memory atomics (1 ns per atomic) and incurs an additional 10% total overhead to accumulate privatized results back to global memory. Each atomic corresponds to 5 floating-point operations. Assuming all updates still effectively serialize, what is the maximum floating-point throughput (in FLOP/s)?
Answer: 4545454545
Explanation: Effective per-atomic time = 1 ns x 1.1 = 1.1 ns ⇒ atomic throughput ~ 1/1.1 ns ~ 909,090,909 ops/s. FLOP/s ~ 909,090,909 x 5 ~ 4,545,454,545.
Question: To atomically add the value of an integer variable Partial to a global-memory integer variable Total, which statement is correct? Use the signature: int atomicAdd(int* address, int val);
A. atomicAdd(Total, 1);
B. atomicAdd(&Total, &Partial);
C. atomicAdd(Total, &Partial);
D. atomicAdd(&Total, Partial);
Answer: D
Explanation: atomicAdd expects a pointer to the destination and a by-value increment: atomicAdd(&Total, Partial).
Question: Consider the CUDA kernel below and assume a single block with blockDim.x = 512 threads, warpSize = 32, and an input array of 1024 elements (each thread initially handles two elements via i = 2*threadIdx.x). During the 5th iteration of the loop (i.e., stride = 16), how many warps in the block have control-flow divergence?

```cpp
__global__ void simple_sum_reduction_kernel(float* input, float* output){
    unsigned int i = 2 * threadIdx.x;
    for (unsigned int stride = 1; stride <= blockDim.x; stride *= 2){
        if (threadIdx.x % stride == 0){
            input[i] += input[i + stride];
        }
        __syncthreads();
    }
    if (threadIdx.x == 0)
        *output = input[0];
}
```

Answer: 16
Explanation: There are 512 threads -> 16 warps. At stride=16, in each warp exactly 2 of 32 lanes execute (threads whose threadIdx.x is a multiple of 16), so every warp diverges -> 16 warps.
Question: Consider the CUDA kernel below and assume a single block with blockDim.x = 512 threads, warpSize = 32, and an input array of 1024 elements. During the 5th iteration (strides: 512, 256, 128, 64, **32**), how many warps have control-flow divergence?

```cpp
__global__ void ConvergentSumReductionKernel(float* input, float* output) {
    unsigned int i = threadIdx.x;
    for (unsigned int stride = blockDim.x; stride >= 1; stride /= 2) {
        if (threadIdx.x < stride) {
            input[i] += input[i + stride];
        }
        __syncthreads();
    }
    if(threadIdx.x == 0) {
        *output = input[0];
    }
}
```

Answer: 0
Explanation: At stride=32, exactly lanes 0-31 (one full warp) are active and all take the same branch; other warps are fully inactive. No warp mixes taken/not-taken paths -> 0 divergent warps.
Question: You want a *reversed* convergent access pattern where active threads add from lower indices (i - stride) instead of higher (i + stride). Starting from the kernel shown below, pick the option that correctly applies this change **without altering the iteration order**. Assume the input has 1024 elements and blockDim.x=512.

Original kernel:
```cpp
__global__ void ConvergentSumReductionKernel(float* input, float* output) {
    unsigned int i = threadIdx.x;
    for (unsigned int stride = blockDim.x; stride >= 1; stride /= 2) {
        if (threadIdx.x < stride) {
            input[i] += input[i + stride];
        }
        __syncthreads();
    }
    if (threadIdx.x == 0) {
        *output = input[0];
    }
}
```
Choices (showing only the lines to change):
A. `unsigned int i = threadIdx.x + blockDim.x;` and inside the loop `if (blockDim.x - threadIdx.x <= stride) { input[i] += input[i - stride]; }` and at the end `if (threadIdx.x == blockDim.x - 1) *output = input[i];`
B. Keep `unsigned int i = threadIdx.x;` but change the loop body to `if (threadIdx.x < stride) { input[i] += input[i - stride]; }`
C. `unsigned int i = threadIdx.x + blockDim.x;` and keep the original condition `if (threadIdx.x < stride) { input[i] += input[i - stride]; }`
D. Keep the original kernel unchanged (already reversed by iteration order).

A
B
C
D
Answer: A
Explanation: To read i-stride safely, initialize i one blockDim.x ahead and mirror the active-lane condition. A ensures in-bounds access and preserves convergence; B/C can access negative indices or mis-activate lanes; D does not implement reversal.
Question: You need to modify a block-level sum reduction to compute the **maximum** instead. Assume the GPU supports `atomicMax(float*, float)` and the usual `fmax` intrinsic. Which change set is **correct**?

A. Replace all `+`/`+=` with `fmax(..., ...)` in both the per-thread coarsening stage and the shared-memory tree, and replace the final `atomicAdd(output, value)` with `atomicMax(output, value)`.
B. Replace `+` with `fmax` only in the shared-memory tree; keep `atomicAdd` at the end.
C. Keep the tree as sum; at the end do `*output = fmax(*output, blockMax)` without atomics.
D. Compute max on the host after copying partial sums from device.

A
B
C
D
Answer: A
Explanation: Max must be used consistently in *all* accumulation phases, and the final cross-block combine must be an atomic max. B mixes ops; C is racy; D changes the algorithmic contract.
Question: Consider a coarsened block-level reduction where each thread sums multiple elements spaced by `BLOCK_DIM`. The input length `N` may not be a multiple of the tile span. Which guard pattern prevents out-of-bounds reads while preserving correctness?

A.
```cpp
float sum = 0.0f;
if (i < N) {
  sum = input[i];
  for (unsigned int t = 1; t < COARSE_FACTOR*2; ++t)
    if (i + t*BLOCK_DIM < N) sum += input[i + t*BLOCK_DIM];
}
```
B.
```cpp
float sum = input[i];
for (unsigned int t = 1; t < COARSE_FACTOR*2; ++t)
  if (i + t*BLOCK_DIM < N) sum += input[i + t*BLOCK_DIM];
```
C.
```cpp
float sum = 0.0f;
if (i <= N) {
  sum = input[i];
  for (unsigned int t = 1; t <= COARSE_FACTOR*2; ++t)
    sum += input[i + t*BLOCK_DIM];
}
```
D.
```cpp
float sum = 0.0f;
if (i < N) sum = input[i];
for (unsigned int t = 1; t < COARSE_FACTOR*2; ++t)
  sum += input[i + t*BLOCK_DIM];
```

A
B
C
D
Answer: A
Explanation: A guards both the base element and each strided read. B can read input[i] out of bounds when i ≥ N. C uses off-by-one bounds and unguarded inner reads. D leaves inner reads unguarded.
Question: A single CUDA block of 8 threads performs an inclusive prefix scan on X = [4, 6, 7, 1, 2, 8, 5, 2] using the classic Kogge-Stone pattern. Strides run with values s = 1, 2, 4. For each stride, every thread tid >= s adds the value from position tid - s that was produced in the previous stride, while threads with tid < s keep their current value. After completing all three strides, which vector remains in X?
A. [4, 10, 17, 18, 20, 28, 33, 35]
B. [4, 10, 13, 8, 10, 10, 13, 7]
C. [4, 6, 7, 1, 2, 8, 5, 2]
D. [4, 6, 13, 14, 16, 26, 31, 33]
Answer: A
Explanation: Stride 1 adds neighbors offset by 1, stride 2 adds neighbors offset by 2 drawing from the stride-1 results, and stride 4 adds neighbors offset by 4. The cumulative effect is the inclusive prefix sums [4,10,17,18,20,28,33,35].
Question: For an inclusive Kogge-Stone scan across N = 2048 elements, the theoretical number of floating-point additions is given by N * log2(N) - (N - 1). Compute this value and provide it as an integer.
Answer: 20481
Explanation: log2(2048) = 11, so 2048 * 11 = 22528 and subtracting (2048 - 1) = 2047 gives 22528 - 2047 = 20481 additions.
Question: In the Kogge-Stone scan kernel shown below, assume blockDim.x = 1024 and warp size = 32.\n\n```cpp\n__global__ void koggeStone(float *X, float *Y, unsigned int N) {\n    __shared__ float XY[1024];\n    unsigned int i = blockIdx.x * blockDim.x + threadIdx.x;\n    if (i < N) {\n        XY[threadIdx.x] = X[i];\n    } else {\n        XY[threadIdx.x] = 0.0f;\n    }\n    for (unsigned int stride = 1; stride < blockDim.x; stride *= 2) {\n        __syncthreads();\n        float temp;\n        if (threadIdx.x >= stride) {\n            temp = XY[threadIdx.x] + XY[threadIdx.x - stride];\n        }\n        __syncthreads();\n        if (threadIdx.x >= stride) {\n            XY[threadIdx.x] = temp;\n        }\n    }\n    if (i < N) {\n        Y[i] = XY[threadIdx.x];\n    }\n}\n```\n\nFor how many stride values does control divergence occur within warp 0?
A. 3
B. 5
C. 16
D. 32
Answer: B
Explanation: Warp 0 contains threads 0-31. Divergence happens while stride is 1, 2, 4, 8, or 16 because only a subset of that warp satisfies threadIdx.x >= stride. Once stride reaches 32 or larger, either all warp-0 threads are inactive or all evaluate the branch the same way.
Question: A Kogge-Stone scan processes N = 2048 elements using two blocks of 1024 threads. Within each block, the numbers of active threads at strides s = 1, 2, 4, 8, 16, 32, 64, 128, 256, 512 are 1023, 1022, 1020, 1016, 1008, 992, 960, 896, 768, and 512 respectively. Each active thread performs one addition per stride. After summing both blocks and adding 1024 more additions to propagate block sums, how many additions are executed in total? Provide the integer result.
Answer: 19458
Explanation: Per block the additions sum to 1023+1022+1020+1016+1008+992+960+896+768+512 = 9217. Two blocks contribute 2 * 9217 = 18434 additions, and adding 1024 for inter-block propagation yields 19458.
Question: A Brent-Kung scan on N = 2048 elements uses one block of 1024 threads. During the reduction tree phase, the active thread counts per stride are 1024, 512, 256, 128, 64, 32, 16, 8, 4, 2, 1. During the reverse tree phase, the active counts per stride are 1, 4, 7, 15, 31, 63, 127, 254, 509, 1019. If each active thread executes one addition at its stride, how many additions occur in total across both phases? Provide the integer total.
Answer: 4077
Explanation: The reduction phase contributes 1024+512+256+128+64+32+16+8+4+2+1 = 2047 additions. The reverse phase contributes 1+4+7+15+31+63+127+254+509+1019 = 2030 additions. The combined total is 2047 + 2030 = 4077.
Question: The function below implements co_rank for merging two sorted arrays using zero-based indices.

```cpp
int co_rank(int k, const int* A, int m, const int* B, int n) {
    int i = (k < m) ? k : m;
    int j = k - i;
    int i_low = (0 > (k - n)) ? 0 : (k - n);
    int j_low = (0 > (k - m)) ? 0 : (k - m);
    while (true) {
        if (i > 0 && j < n && A[i - 1] > B[j]) {
            int delta = (i - i_low + 1) >> 1;
            j_low = j;
            j += delta;
            i -= delta;
        } else if (j > 0 && i < m && B[j - 1] >= A[i]) {
            int delta = (j - j_low + 1) >> 1;
            i_low = i;
            i += delta;
            j -= delta;
        } else {
            return i;
        }
    }
}
```

Let A = [1, 7, 8, 9, 10], B = [7, 10, 10, 12], and let C be the merged array of length m + n. For k = 8 (the start of the output suffix C[8:]), which pair (i, j) = (co_rank(k), k - co_rank(k)) does the function compute?
A. (4, 4)
B. (5, 3)
C. (8, 0)
D. (3, 5)
Answer: B
Explanation: The merged array is [1,7,7,8,9,10,10,12]. Position k = 8 is past all five elements taken from A, so co_rank returns i = 5. The remaining offset is j = k - i = 3.
Question: Two sorted arrays of lengths 1030400 and 608000 are merged on the GPU using a basic co-rank kernel. Each thread is assigned exactly 8 output elements (elementsPerThread = 8), and every thread performs two binary searches via co_rank before calling a sequential merge. How many threads in total execute the binary searches when the entire merge is launched? Provide the integer count.
Answer: 204800
Explanation: The merged output has 1030400 + 608000 = 1638400 elements. Dividing by 8 elements per thread gives 1638400 / 8 = 204800 threads, and every thread runs both co_rank searches.
Question: The tiled merge kernel splits work by assigning exactly 8 output elements to each thread within a 1024-thread block. Before processing its tile, thread 0 in every block computes co_rank for the block's starting and ending output indices. Using the same arrays (1030400 and 608000 elements) and 8 elements merged per thread, how many threads in total perform binary searches against global memory? Provide the integer count.
Answer: 200
Explanation: Each 1024-thread block merges 8 x 1024 = 8192 elements. The total output length 1638400 divided by 8192 gives 200 blocks. Only thread 0 in each block issues the two co_rank calls, so 200 threads perform binary searches.
Question: A benchmark with N = 10,000,000 elements produced the timings below (in milliseconds) and GPU speedups versus a CPU quicksort baseline:

- Naive Parallel Radix Sort: time = 14.099, speedup = 149.70x
- Memory-coalesced GPU sort: time = 18.055, speedup = 116.90x
- Memory-coalesced GPU sort (multiradix): time = 20.671, speedup = 102.11x
- Memory-coalesced GPU sort (multiradix + thread coarsening): time = 39.275, speedup = 53.74x
- GPU merge sort: time = 135.828, speedup = 15.54x

Which method delivered the highest speedup?
A. Memory-coalesced GPU sort
B. Memory-coalesced GPU sort (multiradix)
C. Memory-coalesced GPU sort (multiradix + thread coarsening)
D. Naive Parallel Radix Sort
Answer: D
Explanation: 149.70x is the largest reported speedup, achieved by the Naive Parallel Radix Sort entry.
Question: Using the benchmark data above, compute the difference in reported speedup between Naive Parallel Radix Sort (149.70x) and GPU merge sort (15.54x). Give the result rounded to two decimal places as a decimal number (e.g., 12.34).
Answer: 134.16
Explanation: 149.70 - 15.54 = 134.16 when rounded to two decimal places.
Question: A single-kernel variant of the parallel radix sort attempts to synchronize across the entire grid to reuse the total count of zero bits. Beyond roughly 100,000 elements it deadlocks. Why is this approach fragile?
A. Because GPU quicksort launches interfere with the kernel
B. Because the first block must wait for the last block without a device-wide synchronization primitive
C. Because the radix counters overflow 32-bit integers
D. Because thread coarsening increases register pressure beyond limits
Answer: B
Explanation: The README notes that the single-kernel try stalls because the first block needs the global zero count from the last block, effectively requiring a grid-wide barrier that the vanilla CUDA launch cannot provide.
Question: Consider the 4x4 sparse matrix

[ [1, 0, 7, 0],
  [0, 0, 8, 0],
  [0, 4, 3, 0],
  [2, 0, 0, 1] ].

Using zero-based indices and listing nonzeros in row-major order, which triple of arrays encodes this matrix in COO format (row indices, column indices, values)?
A. row=[0,0,1,2,3,3,3], col=[0,2,2,1,0,2,3], val=[1,7,8,4,2,3,1]
B. row=[0,0,1,2,2,3,3], col=[0,2,2,1,2,0,3], val=[1,7,8,4,3,2,1]
C. row=[0,1,1,2,2,3,3], col=[0,2,3,0,2,0,3], val=[1,7,8,4,3,2,1]
D. row=[0,0,2,2,3,3,3], col=[0,2,1,2,0,2,3], val=[1,7,4,3,2,1,8]
Answer: B
Explanation: Row-major enumeration yields nonzeros (0,0),(0,2),(1,2),(2,1),(2,2),(3,0),(3,3) with values [1,7,8,4,3,2,1]; option B matches this ordering exactly.
Question: The same 4x4 matrix is stored in CSR format using zero-based indexing and row-major ordering of nonzeros. What is the CSR row pointer array rowPtr? Provide it as a tuple (r0,r1,r2,r3,r4).
Answer: (0,2,3,5,7)
Explanation: Row lengths are [2,1,2,2]; the prefix sums give rowPtr = [0,2,3,5,7].
Question: You know only that a sparse matrix has m rows, n columns, and z nonzeros. Which statement correctly identifies the additional information needed to determine memory usage for different formats?
A. COO storage cannot be determined from m, n, z because row indices depend on padding information.
B. CSR needs the maximum row length to size its row pointer array.
C. Both ELL and JDS require the maximum number of nonzeros in any row (and related row ordering details) to size their auxiliary arrays.
D. JDS needs only m, n, z because the iterPtr array always has length m + 1.
Answer: C
Explanation: COO and CSR can be sized directly from z and m (rowPtr has length m+1). ELL and JDS must know the longest row to size padding/iterPtr, and JDS also needs the row permutation order.
Question: A scale-free graph with 20,000 vertices was benchmarked using several BFS variants. The reported runtimes (milliseconds) and GPU speedups versus a sequential baseline were:

- Sequential BFS: 10.26 ms (reference)
- Push Vertex-Centric BFS: 1.44 ms (7.12x)
- Pull Vertex-Centric BFS: 0.44 ms (23.53x)
- Edge-Centric BFS: 0.19 ms (54.81x)
- Frontier-based BFS: 2.49 ms (4.11x)
- Optimized Frontier-based BFS: 2.60 ms (3.95x)
- Direction-Optimized BFS: 0.48 ms (21.54x)

Which variant achieved the highest reported speedup?
A. Pull Vertex-Centric BFS
B. Edge-Centric BFS
C. Direction-Optimized BFS
D. Push Vertex-Centric BFS
Answer: B
Explanation: Edge-Centric BFS reached 54.81x, larger than the pull (23.53x), direction-optimized (21.54x), or push (7.12x) variants.
Question: For the scale-free graph with 10,000 vertices, the benchmark reported Sequential BFS = 4.71 ms and Direction-Optimized BFS = 0.35 ms. What is the absolute time saved (Sequential minus Direction-Optimized) in milliseconds, rounded to two decimals?
Answer: 4.36
Explanation: Time saved = 4.71 ms - 0.35 ms = 4.36 ms.