14. Performance Engineering and Technical Decision-MakingIntroduction and ContextPerformance is not a property that can be added reliably at the end of software development. It emerges from algorithms, data structures, object lifetimes, memory layout, compiler decisions, operating-system behaviour, hardware architecture, and workload characteristics. In C++, these factors interact particularly closely because the language offers both high-level abstractions and low-level control. A program may use expressive classes, generic algorithms, smart pointers, and standard containers while still permitting precise control over allocation, representation, and execution. Performance engineering is the disciplined practice of understanding, measuring, improving, and governing these interactions. It is broader than optimisation. Optimisation often refers to changing code to make it faster or smaller. Performance engineering includes the prior questions: What does “fast enough” mean? Which workload matters? How should latency be measured? Is the bottleneck computation, memory, input/output, synchronisation, or allocation? What safety or maintenance cost accompanies a proposed improvement? A technically elegant optimisation that reduces execution time by two percent while substantially increasing defect risk may be a poor engineering decision. For postgraduate C++ practitioners, performance must therefore be treated as an evidence-based design concern. Complexity analysis predicts how a solution scales; profiling reveals where time is actually spent; memory analysis explains footprint and locality; benchmarking compares alternatives under controlled conditions; and decision frameworks connect measurements with business, operational, and safety requirements. Learning ObjectivesAfter completing this chapter, the learner should be able to: 1. Distinguish asymptotic complexity from measured performance and explain the limits of each. 2. Analyse time, space, allocation, and communication costs in C++ programs. 3. Explain object representation, alignment, padding, locality, cache behaviour, and false sharing. 4. Design repeatable microbenchmarks and interpret their results critically. 5. Use profiling and measurement to locate bottlenecks rather than guessing. 6. Compare alternatives using performance, safety, maintainability, cost, and operational risk. 7. Apply a structured optimisation workflow to realistic C++ systems. 8. Recognise common measurement errors, ethical concerns, and emerging performance practices. Performance as a Multi-Dimensional Engineering PropertyA useful performance requirement must identify both a metric and a workload. Common metrics include throughput, latency, tail latency, memory footprint, startup time, energy consumption, and cost per operation. Throughput measures completed work per unit time; latency measures the duration of an individual operation. They are related but not interchangeable. A server can achieve high average throughput while occasionally producing unacceptable 99th-percentile latency. Performance targets should also state the conditions under which they apply. “The application must respond within 20 milliseconds” is incomplete unless the request type, data size, concurrency, hardware, and percentile are specified. A stronger requirement might state that 99 percent of read requests involving a specified dataset must complete within 20 milliseconds at a stated concurrency level. Three distinctions are especially important: - Capacity is the amount of work a system can sustain.
- Responsiveness is how quickly an individual operation completes.
- Efficiency is the resources consumed per unit of useful work.
Improving one dimension can harm another. Increasing worker threads may improve throughput until contention and cache interference cause latency to rise. Caching may reduce computation but increase memory consumption and invalidation complexity. A custom allocator may reduce allocation time but complicate ownership and debugging. Complexity Analysis: Prediction Before MeasurementAsymptotic analysis describes how resource requirements grow as input size changes. For example, searching an unsorted vector requires O(n) comparisons in the worst case, while binary search on sorted data requires O(log n). Inserting into the middle of a contiguous vector is generally O(n) because elements must be moved, whereas appending is amortised O(1) when capacity is available. Big-O notation expresses an upper growth class while ignoring constants and lower-order terms. This abstraction is valuable for rejecting algorithms that scale poorly, but it does not predict absolute running time. A linear scan over a compact array may outperform a logarithmic tree search for small or moderate datasets because of locality, branch behaviour, and lower constant factors. Average, worst-case, and amortised complexity answer different questions. A hash table may offer expected O(1) lookup but experience poor behaviour under collisions or resizing. A dynamic array’s individual reallocation can be O(n), while the average cost over a long sequence of appends is amortised O(1). In systems with service-level obligations, worst-case or high-percentile behaviour may matter more than the average. Space complexity includes auxiliary data structures, recursion depth, object storage, temporary allocations, and sometimes external resources such as file buffers. A function with O(n) space may be unacceptable when n is large, even if its time is excellent. Conversely, an O(n log n) algorithm that uses bounded additional space may be preferable in a memory-constrained environment. Complexity analysis should be performed at several levels: 1. Algorithmic level: growth in operations as input increases. 2. Data-structure level: lookup, insertion, movement, and allocation behaviour. 3. Object level: construction, destruction, copying, and ownership costs. 4. System level: scheduling, I/O, synchronisation, and communication costs. The practical conclusion is not to choose complexity notation or measurement exclusively. Complexity identifies scaling risks; measurement identifies actual dominant costs under representative workloads. C++ Cost Models and Abstraction BoundariesC++ permits abstractions that can be highly efficient, but efficiency depends on how they are used. A range-based loop over a container can be as efficient as an indexed loop, while an abstraction involving virtual dispatch, dynamic allocation, or type erasure may introduce measurable costs. These costs are not automatically unacceptable. The correct question is whether they matter in the relevant execution path. Object construction and destruction can be significant when millions of short-lived objects are created. Copying a large value type may be expensive, whereas moving it may transfer resources cheaply; however, a move is not necessarily free. Passing by value can sometimes enable efficient moves and simplify interfaces, while passing by reference avoids copying when ownership is not intended. These choices should follow ownership semantics and measured needs rather than stylistic rules applied without context. The standard library provides important performance tools. `std::vector` offers contiguous storage and usually excellent traversal locality. `std::deque` provides efficient insertion at both ends but has a less uniform memory layout. `std::list` offers stable node references and insertion properties, but each node may require an allocation and pointer traversal can be cache-unfriendly. Associative containers differ in ordering, lookup strategy, allocation pattern, and memory overhead. Choosing a container is therefore a performance and semantic decision together. Capacity management is another practical concern. Calling `reserve` on a vector when an approximate final size is known can prevent repeated reallocations. Excessive reservation, however, may waste memory and reduce locality. `shrink_to_fit` is a non-binding request and may not produce the expected result. Such operations should be considered in relation to lifetime, workload, and memory pressure. Memory Layout, Locality, and Cache BehaviourThe performance of modern processors is often limited by data movement rather than arithmetic. Processor registers and caches are much faster than main memory, so a program benefits when it accesses data that is already nearby in the memory hierarchy. Temporal locality means recently used data is likely to be used again. Spatial locality means data near a recently accessed location is likely to be used soon. Contiguous storage usually supports spatial locality. Traversing a `std::vector<int>` sequentially allows hardware prefetching and efficient cache-line use. A linked list may perform the same number of logical operations but follow pointers to unrelated memory locations, causing cache misses and translation overhead. This is one reason asymptotically similar approaches can have very different measured performance. C++ object layout includes members, alignment requirements, padding, and possibly virtual-function metadata. Reordering members can reduce padding in some structures, but layout changes may affect binary compatibility, serialisation, and readability. The `sizeof` operator reports the size of an object representation, which may exceed the sum of member sizes. `alignof` reports the alignment requirement. These properties matter when designing arrays of millions of objects or interfaces shared across binary boundaries. An array of structures stores complete objects consecutively. It is convenient when most fields are used together. A structure of arrays stores each field in a separate array and can improve locality when a computation processes only selected fields. For example, a physics calculation that repeatedly updates positions but rarely reads labels may benefit from separating numeric coordinates from metadata. The transformation can improve cache use but may increase complexity and complicate invariants. Multithreaded programs introduce further issues. False sharing occurs when independent variables modified by different threads occupy the same cache line, causing repeated coherence traffic. Padding or appropriate alignment can reduce this effect, but padding increases memory use and should be justified by measurement. Atomic operations, locks, and memory-ordering choices also have performance consequences, but correctness must never be traded away merely for speed. Allocation, Ownership, and Lifetime CostsDynamic allocation involves more than obtaining bytes. It may require allocator metadata, synchronisation, alignment handling, page management, and eventual reclamation. Frequent small allocations can dominate execution time and fragment memory. They can also increase tail latency because allocator behaviour may vary under contention. Modern C++ ownership tools improve safety. `std::unique_ptr` expresses exclusive ownership with low overhead and normally does not require reference counting. `std::shared_ptr` supports shared ownership but introduces control-block storage and reference-count operations. It should not be selected merely as a default replacement for all pointers. `std::weak_ptr` helps observe shared objects without extending their lifetime. Raw pointers and references remain appropriate for non-owning access when the lifetime contract is clear. Useful allocation strategies include reserving container capacity, reusing buffers, object pools, arenas, monotonic allocation resources, and custom allocators. An arena can make bulk destruction inexpensive, but individual object reclamation may not be available. A pool can make fixed-size allocation predictable, but it requires careful handling of alignment, exhaustion, and thread safety. Polymorphic allocators can decouple container interfaces from allocation policy, supporting configuration without rewriting data structures. Memory optimisation must consider lifetime and peak usage, not only total allocation count. A program may allocate little in total but retain a large working set. Conversely, temporary allocation may be harmless if it occurs outside latency-critical paths. Tools such as sanitizers, heap profilers, allocation tracing, and operating-system monitors can reveal leaks, use-after-free errors, fragmentation, and unexpected retention. Profiling: Finding the Work That MattersProfiling measures program behaviour during execution and attributes cost to functions, call paths, lines, threads, or hardware events. It is the central antidote to intuition-driven optimisation. A programmer may suspect a sorting routine, while profiling shows that logging, memory allocation, lock contention, or conversion code dominates. Sampling profilers periodically interrupt or inspect running threads and record instruction locations. They usually have lower overhead and are suitable for observing production-like workloads. Instrumentation profilers record explicit entry and exit events, providing detailed timing but potentially perturbing execution. Tracing systems record events such as scheduling, I/O, allocations, and synchronisation, enabling analysis of interactions across components. Hardware performance counters can report cache misses, branch mispredictions, stalled cycles, instructions retired, and other events. These counters require careful interpretation because events are processor-specific and may be multiplexed or sampled. A high cache-miss count is evidence of a memory-access issue, not proof that changing one data structure will solve the complete problem. A sound profiling process includes the following stages: 1. Define the performance question and acceptance criterion. 2. Build with appropriate symbols and a configuration representative of deployment. 3. Select a realistic workload and warm-up procedure. 4. Measure a baseline several times. 5. Identify dominant costs and investigate their causes. 6. Change one meaningful factor where practical. 7. Re-measure using the same protocol. 8. Validate correctness, resource use, and effects on other workloads. 9. Record the result and decide whether the improvement justifies its cost. Profiling results should be interpreted statistically. A single run can be distorted by scheduling, background processes, thermal behaviour, page faults, or random input. Median time describes a typical run, while high percentiles expose tail behaviour. Confidence intervals or distributions are preferable to unsupported claims based on one measurement. Benchmarking Alternatives with Experimental DisciplineA benchmark is an experiment, not merely a timer placed around a function. A credible benchmark specifies the operation, input distribution, data size, environment, compiler settings, repetitions, warm-up policy, and reported statistics. It also prevents the compiler from eliminating or transforming the work in a way that makes the measurement meaningless. Microbenchmarks are valuable for comparing a narrow operation, such as two parsing strategies or container traversal patterns. They can mislead when their environment does not resemble the complete application. A benchmark that fits entirely in cache may favour an approach that fails on production-scale data. Similarly, benchmarking an isolated function may omit allocation contention, network delays, lock interactions, or branch patterns generated by real input. Macrobenchmarks and workload tests measure larger user-visible operations. They are more representative but harder to control and diagnose. Both forms are useful: microbenchmarks explain mechanisms; macrobenchmarks establish system relevance. Important experimental controls include: - using representative data distributions rather than only favourable inputs;
- separating setup and measurement when setup is not part of the target operation;
- preventing dead-code elimination and unintended compiler optimisation of the test;
- reporting compiler, library, hardware, operating-system, and build configuration;
- repeating tests and examining variance;
- testing cold-cache and warm-cache conditions when both occur in practice;
- measuring memory, allocations, and tail latency in addition to mean time.
A benchmark result is conditional evidence. It supports a decision within the tested conditions; it does not establish a universal ranking of implementations. A Decision Framework for Trade-offsPerformance engineering becomes strategic when alternatives have competing consequences. A useful decision record begins with a requirement and a bottleneck, then compares candidate interventions across several dimensions: | Dimension | Questions to ask | |---|---| | Speed and latency | Does the change improve the target metric and workload? | | Scalability | Does it remain effective as data or concurrency grows? | | Memory and energy | What additional footprint or power cost is introduced? | | Safety | Does it affect ownership, bounds, race, or exception guarantees? | | Maintainability | Can ordinary developers understand, test, and modify it? | | Portability | Does it depend on a compiler, processor, or platform detail? | | Operational cost | What are deployment, monitoring, and support consequences? | | Reversibility | Can the decision be rolled back if assumptions change? | The performance budget approach assigns limits to components. For example, a request may have a latency budget divided among parsing, validation, storage access, computation, and response generation. This prevents one component from consuming the entire budget and encourages explicit prioritisation. A cost-of-delay perspective asks what happens if optimisation is postponed. If current performance already meets requirements, complexity may not be justified. If growth forecasts indicate an imminent capacity limit, early architectural action may be prudent. A total-cost-of-ownership perspective includes engineering time, defects, hardware, cloud usage, energy, incident response, and future maintenance. The best solution is often not the fastest measured alternative. It is the alternative that satisfies the required constraint with an acceptable risk profile. Optimisation should be proportional to the importance of the path, the stability of the workload, and the durability of the benefit. Applied Scenario: Reducing Latency in a C++ Event ProcessorBackgroundThis illustrative case concerns a C++ service that processes financial-market event messages. It receives batches, validates records, updates an in-memory state, and publishes selected results. The service meets its average latency target but violates the 99th-percentile target during periods of high message volume. Situation and Initial HypothesesThe team initially suspects the validation algorithm because it performs several searches. A proposed solution is to rewrite the validation layer using specialised low-level code. Before accepting that proposal, the team defines the target: reduce 99th-percentile batch latency without increasing error rates or exceeding the memory limit. AnalysisA production-like replay is profiled using sampling and allocation tracing. The results show that validation consumes only a modest fraction of CPU time. The largest contributors are repeated allocation of temporary message objects, cache-unfriendly traversal of a linked structure, and lock contention around publication. High-percentile latency is strongly associated with allocation bursts and lock waiting. The team establishes a baseline over repeated runs, records median and 99th-percentile latency, allocation counts, peak resident memory, and throughput. It also verifies that the replay preserves realistic message sizes and ordering. Strategy and SolutionFirst, temporary objects are replaced with reusable buffers whose ownership remains local to the processing thread. A vector reserves capacity based on observed batch sizes, while an arena is used for objects that share the batch lifetime. Second, the linked structure is replaced with a contiguous representation because the dominant operation is sequential traversal rather than stable insertion. Third, publication is redesigned so that workers prepare results privately and hold the shared lock only during a short exchange. The team does not immediately use custom assembly or remove safety checks. Each change is isolated, reviewed, tested with sanitizers, and benchmarked against the baseline. A regression benchmark is added to continuous integration, while a production dashboard tracks latency percentiles and memory use. OutcomeThe changes reduce allocation pressure and waiting time, improving tail latency under the target workload. The contiguous representation increases memory locality but requires an explicit mapping for identifiers, so the design documentation records this trade-off. The team retains the original validation implementation because profiling did not show it to be the limiting factor. Learning PointsThe central lesson is methodological: the first plausible bottleneck was not the actual bottleneck. The successful intervention combined profiling, data-layout reasoning, lifetime management, and synchronisation redesign. It also preserved correctness and created monitoring so that the improvement could be verified after deployment. The case demonstrates why performance decisions should be traceable to measurements and explicit requirements. Common Errors, Risks, and LimitationsPremature optimisation is a frequent error. Developers may complicate interfaces or sacrifice clarity before establishing a measurable need. The opposite error is also serious: assuming that hardware is fast enough and ignoring an architectural bottleneck until change becomes expensive. Other common mistakes include measuring debug builds, excluding allocation or startup costs unintentionally, using unrealistic inputs, trusting averages when tail latency matters, comparing implementations with different compiler settings, and treating one machine’s result as universal. Benchmark harnesses themselves may introduce overhead or allow the compiler to remove the operation being tested. Unsafe optimisation can create buffer overflows, lifetime errors, data races, integer overflow, and incorrect assumptions about object representation. Reliance on undefined behaviour is not a legitimate performance technique. Implementation-specific layout, compiler intrinsics, and processor instructions may be appropriate behind a well-tested portability boundary, but they increase maintenance and verification obligations. Measurement also has limitations. Profilers perturb execution, hardware counters can be difficult to interpret, and controlled benchmarks cannot reproduce every production condition. Performance conclusions should therefore be stated with scope, assumptions, and uncertainty. Professional, Ethical, and Governance ConsiderationsPerformance work can affect fairness, safety, privacy, and operational reliability. Reducing computation by weakening validation may permit malformed or discriminatory outcomes. Disabling audit logging may improve latency while violating regulatory or organisational requirements. A performance target that encourages excessive data collection can create privacy risks. Engineering governance should require reproducible evidence for significant optimisation decisions, review of safety and security implications, and monitoring after release. In safety-critical or regulated systems, worst-case timing, traceability, deterministic behaviour, and independent verification may take precedence over average speed. Energy consumption and hardware utilisation are also professional concerns because inefficient software can increase infrastructure cost and environmental impact. Performance dashboards should avoid rewarding teams solely for a single metric. A lower latency figure accompanied by higher failure rates, memory exhaustion, or reduced maintainability is not an unqualified success. Balanced indicators support responsible decision-making. Automation, Tooling, and Emerging DirectionsContemporary C++ development combines compiler diagnostics, static analysis, sanitizers, profilers, benchmark frameworks, continuous integration, and production observability. Automated regression benchmarks can detect performance changes early, although noisy environments require thresholds and statistical interpretation rather than rigid single-run comparisons. Profile-guided optimisation uses execution data to inform compiler choices such as inlining and code layout. Link-time optimisation can expose opportunities across translation units. Hardware-aware programming increasingly considers vectorisation, heterogeneous processors, non-uniform memory access, and specialised accelerators. These techniques are valuable when workload and deployment conditions justify them. Machine learning can assist anomaly detection, workload classification, and prediction of resource demand, but automated recommendations still require human validation. A model may identify correlation without explaining causation, and training data may not represent future workloads. Emerging C++ facilities and library designs continue to improve compile-time computation, ranges, executors, and allocation control, but adoption should be guided by toolchain maturity and project requirements rather than novelty. Best-Practice WorkflowA durable performance practice can be summarised as follows: 1. Express the user or system requirement as a measurable target. 2. Understand the workload, scale, and deployment environment. 3. Analyse algorithmic and memory complexity. 4. Establish a reproducible baseline. 5. Profile the complete system before changing code. 6. Identify the bottleneck and formulate a causal hypothesis. 7. Choose the least complex intervention likely to meet the target. 8. Benchmark the change with representative data and repeated trials. 9. Check correctness, safety, memory, energy, and maintainability. 10. Deploy gradually, monitor production behaviour, and document the decision. This workflow turns optimisation from personal opinion into an engineering activity. It also creates institutional knowledge: future developers can understand not only what was changed, but why it was changed and under which assumptions it remains valid. Chapter SummaryPerformance engineering in C++ combines complexity analysis, measurement, memory reasoning, benchmarking, and strategic judgement. Big-O analysis reveals growth behaviour but cannot replace empirical evidence. Container choice, object lifetime, allocation policy, layout, locality, cache effects, synchronisation, and compiler configuration all influence real performance. Profiling identifies dominant costs, while disciplined benchmarks compare alternatives under explicit conditions. Sound decisions consider latency, throughput, memory, safety, maintainability, portability, energy, and total cost rather than speed alone. The recommended workflow is to define a measurable requirement, establish a baseline, profile, change the bottleneck, validate experimentally, and monitor the result in operation. Key TermsAsymptotic complexity: A description of how resource requirements grow as input size increases. Amortised complexity: Average cost per operation over a sequence that may contain occasional expensive operations. Latency: Time taken to complete an individual operation. Throughput: Amount of useful work completed per unit of time. Tail latency: High-percentile response time, such as the 99th percentile. Working set: Data actively needed by a program during a period of execution. Temporal locality: Likelihood that recently accessed data will be accessed again. Spatial locality: Likelihood that nearby data will be accessed soon. Cache miss: An access for which the required data is not present in the relevant cache. False sharing: Performance degradation caused when threads modify separate data sharing one cache line. Allocation overhead: Time and metadata cost associated with obtaining and releasing dynamic storage. Profiling: Collecting execution data to locate time, memory, or hardware costs. Sampling profiler: A profiler that periodically records execution locations with relatively low overhead. Instrumentation: Explicit recording of program events such as function entry and exit. Microbenchmark: A controlled test of a narrow operation or component. Macrobenchmark: A benchmark representing a larger workflow or user-visible operation. Performance budget: An allocated limit for a metric across system components. Profile-guided optimisation: Compiler optimisation informed by observed execution profiles. Review QuestionsShort Answer Questions1. Why can an O(log n) algorithm be slower than an O(n) algorithm for some workloads? 2. Distinguish throughput from latency and explain why both may be needed in a requirement. 3. What is false sharing, and why can it harm multithreaded C++ programs? 4. Why does `std::vector` often exhibit better traversal performance than `std::list`? 5. State two reasons why a single benchmark run is insufficient evidence. Long Answer Questions1. Explain how complexity analysis, profiling, and benchmarking complement one another in performance engineering. 2. Discuss the effects of object layout, alignment, padding, and locality on C++ application performance. 3. Compare ownership and allocation considerations involving `std::unique_ptr`, `std::shared_ptr`, pools, and arenas. 4. Describe a rigorous workflow for optimising a latency-sensitive C++ application. 5. Explain how safety, maintainability, portability, and total cost should influence an optimisation decision. Analytical / Application Questions1. A vector traversal is fast for 10,000 records but slow for 100 million records, while a linked structure shows the opposite result in a small test. Design an experiment that determines whether locality or algorithmic complexity is responsible. 2. A service has acceptable median latency but unacceptable 99th-percentile latency during load spikes. Identify measurements and hypotheses you would investigate before changing the algorithm. 3. A proposed custom allocator improves benchmark time by 12 percent but introduces complex lifetime rules and is difficult to use safely. Prepare a decision analysis determining whether to adopt, reject, or isolate the change. 22. Multiple Choice Questions with AnswersQuestion 1Which statement best describes the relationship between Big-O analysis and benchmarking? A. Big-O analysis makes benchmarking unnecessary. B. Benchmarking is always more reliable than complexity analysis. C. Complexity analysis predicts scaling, while benchmarking measures behaviour under specified conditions. D. Both methods produce identical results when input size is large. Correct Answer: C. Complexity analysis predicts scaling, while benchmarking measures behaviour under specified conditions. Question 2Why may sequential traversal of a std::vector outperform traversal of a linked list with similar asymptotic complexity? A. Vectors never require memory allocation. B. Vectors generally provide better spatial locality and cache usage. C. Linked lists always perform logarithmic traversal. D. Vectors automatically use multiple threads. Correct Answer: B. Vectors generally provide better spatial locality and cache usage. Question 3Which metric is most directly concerned with occasional slow responses under a service-level objective? A. Average object size B. 99th-percentile latency C. Source-code length D. Compiler version Correct Answer: B. 99th-percentile latency Question 4What is a principal risk of benchmarking only a small, cache-resident dataset? A. It necessarily disables compiler optimisation. B. It may favour an implementation that performs poorly at production scale. C. It measures only disk throughput. D. It prevents measurement of arithmetic operations. Correct Answer: B. It may favour an implementation that performs poorly at production scale. Question 5Which change most directly reduces repeated vector reallocations when the approximate final size is known? A. Calling reserve with an appropriate capacity B. Replacing the vector with a raw pointer without allocation C. Adding virtual functions to the element type D. Using a linked list for every workload Correct Answer: A. Calling reserve with an appropriate capacity Question 6A profiler shows that most high-percentile latency is caused by lock waiting rather than computation. Which intervention is most directly relevant? A. Replacing all integer arithmetic with floating-point arithmetic B. Reducing the duration or frequency of critical-section ownership C. Increasing source-code comments only D. Changing Big-O notation in the design document Correct Answer: B. Reducing the duration or frequency of critical-section ownership Question 7Which statement about std::shared_ptr is correct? A. It always has zero overhead compared with a raw pointer. B. It expresses exclusive ownership without any control block. C. It supports shared ownership but may introduce control-block and reference-count costs. D. It prevents all memory leaks in cyclic ownership graphs. Correct Answer: C. It supports shared ownership but may introduce control-block and reference-count costs. Question 8What does false sharing describe? A. Two algorithms producing the same output B. Threads modifying independent variables located on the same cache line C. A benchmark using a false input type D. A compiler rejecting a shared library Correct Answer: B. Threads modifying independent variables located on the same cache line Question 9Which is the strongest reason to preserve a measured but slower implementation instead of adopting a faster one? A. The slower implementation has no source code. B. The faster implementation violates safety or maintainability requirements without necessary benefit. C. The slower implementation uses more memory in every case. D. The faster implementation has a shorter function name. Correct Answer: B. The faster implementation violates safety or maintainability requirements without necessary benefit. Question 10What should be done immediately before claiming that an optimisation is successful? A. Remove all correctness tests. B. Verify the change with the same representative workload and check side effects such as memory and correctness. C. Assume the result will generalise to every processor. D. Measure only the fastest single run. Correct Answer: B. Verify the change with the same representative workload and check side effects such as memory and correctness.
|