{'fraction_non_alphanumeric': 0.02405543208262518, 'fraction_numerical': 0.007735215932365887, 'mean_word_length': 4.825717187103326, 'pattern_counts': {'":': 0, '<': 1, '<?xml version=': 0, '>': 0, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
Do Your Cores Play Nicely? A Portable Framework for Multi-core Interference Tuning and Analysis

Multi-core architectures can be leveraged to allow independent processes to run in parallel. However, due to resources shared across cores, such as caches, distinct processes may interfere with one another, e.g. affecting execution time. Analysing the extent of this interference is difficult due to: (1) the diversity of modern architectures, which may contain different implementations of shared resources, and (2) the complex nature of modern processors, in which interference might arise due to subtle interactions. To address this, we propose a black-box auto-tuning approach that searches for processes that are effective at causing slowdowns for a program when executed in parallel. Such slowdowns provide lower bounds on worst-case execution time; an important metric in systems with real-time constraints. Our approach considers a set of parameterised"enemy"processes and"victim"programs, each targeting a shared resource. The autotuner searches for enemy process parameters that are effective at causing slowdowns in the victim programs. The idea is that victim programs behave as a proxy for shared resource usage of arbitrary programs. We evaluate our approach on: 5 different chips; 3 resources (cache, memory bus, and main memory); and consider several search strategies and slowdown metrics. Using enemy processes tuned per chip, we evaluate the slowdowns on the autobench and coremark benchmark suites and show that our method is able to achieve slowdowns in 98% of benchmark/chip combinations and provide similar results to manually written enemy processes.


Introduction
Multi-core processors have seen widespread adoption, with nearly every consumer device containing more than one independent processing unit. However, due to shared resources and their corresponding arbitration mechanisms, e.g. cache hierarchies and protocols, reasoning about program behaviours on multi-core processors can be significantly more complex than on their single-core predecessors. Interference can affect nonfunctional properties of otherwise entirely separate processes, e.g. the execution time of a program on a multi-core processor can vary greatly depending on shared resource contention. Because of these issues, the Worst Case Execution Time (WCET) of an application on a multi-core chip is difficult to derive, and as a result, multi-core processors remain challenging to deploy in systems with hard or soft real-time constraints.
Because of this, prior work has identified interference paths, where the contention and arbitration of shared resources might impact program execution time [15,20,21,7,8]. Components of interference paths include caches, memory buses, and main memory systems. Although not widely adopted, various schemes have been proposed to limit this interference. Such schemes require either hardware support, e.g. cache partitioning [18], or invasive software modifications, e.g. bank-aware memory allocation and bandwidth reservation [27,26]. However, even with these schemes, interference can still be substantial [24]. As a result, there is immediate and pragmatic interest in detecting and quantifying interference effects rather than aiming to mitigate them entirely.
In this vein, various techniques have been investigated to quantify the effects of interference on real-time properties, and evaluated for specific multi-core architectures [22,19,13]. Typically, work in this domain consists of: manually developing small programs designed exclusively to stress an interference path (called enemy processes in this work), executing a set of enemy processes on all but one core of a multi-core system (called a hostile environment in this work), and evaluating the execution time of a sequential Software Under Test (SUT) on the remaining core. The slowdown observed in the SUT from the hostile environment is a useful quantification of interference effects.
This prior work requires the manual design of hostile environments and corresponding enemy processes, presenting two immediate limitations. First, manual enemy process design is not portable across architectures, as different architectures may have different implementations, or their shared resources may be configured differently. Thus, manual effort is required for each new target architecture. Second, hand-tuned enemy processes may not be as effective as possible, due to subtle interactions that are difficult to derive from available documentation, and thus unlikely to be considered by human designers.
In this work, we aim to address both limitations. The heart of our contribution is an auto-tuning method that can tune enemy process parameters to be effective at slowing down an SUT. For each interference path considered, our approach takes a parameterised enemy process (called an enemy template) and a corresponding victim program, which is designed to be especially vulnerable to the particular interference path. We then employ auto-tuning to search for enemy process parameters that are effective at causing a slowdown in the corresponding victim program. After obtaining tuned enemy   processes, we employ a second level of tuning over all victim programs to obtain a combination of enemy processes, which can be deployed as a hostile environment. The tuning approach can be run on many chips using the same enemy templates and victim programs to produce chip-specialised hostile environments.
We illustrate the problems with manually-designed enemy processes, and explain at a high-level how our auto-tuning approach overcomes these limitations, using an example. Figure 1a shows a simple vector addition program. The SIZE parameter can be set to a value (in this case we use 16K) such that the vector data is not able to fit in a entirely in a core-local cache; thus, memory accesses must go through shared caches at some point. Suppose we want to assess the potential interference between this program (the SUT) running on one core and a set of independent programs running on other cores. The program of  Figure 1b is an example of an enemy process: the sole task of the program is to exercise an interference path. This particular enemy process is designed to cause cache contention by looping over a memory region the size of the shared cache and accessing memory at a stride of the cache line size. To explore potential interference, the execution time of the SUT executing on one core can be measured while multiple instances of the cache enemy processes are running on other cores in the system. As the enemy process code of Figure 1b shows, there are three parameters that need to be instantiated: (1) size of the shared cache; (2) cache line size; and (3) memory instructions, i.e. loads or stores, used to access memory. To provide suitable values, the target processor must be known. For example, on a 4-core Raspberry Pi 3 B (abbreviated to Pi3), the shared cache size is 512KB, the cache line is 64 bytes and we use a single store followed by a load as the instructions. The execution time of the SUT shows a 1.14× slowdown when executed in parallel with this enemy processes running on the three additional cores. Now, if we consider a different processor, say a 4-core Intel Joule 570X (abbreviated to 570X), we might try the same experiment using the same parameters from the Pi3. In this case, we observe a slowdown of 1.06×. We then might try a different enemy process, tuned to the architectural details of the 570X. This changes the size of the shared cache to 2048KB but keeps the same cache line size. With this new enemy process, a much greater slowdown of 1.36× is observed. However, when the 570X enemy process is used on the Pi3, the slowdown observed is 1.14×, exactly the same as the original Pi3 enemy process. Since the Pi3 and the 570X have the same same cache line size and the same associativity but different cache sizes, the enemy processes will access the cache in a simillar manner but the Pi3 enemy process will not access the entire 570X cache.

Example of Portability Limitation
These results are intuitive: enemy processes tuned to a particular architecture will be less effective when running on a different architecture. Prior work in this area has largely focused on such enemy processes, i.e. hand-tuned for a particular architecture. In the case where enemy processes are written at the assembly level, the situation is even worse: enemy processes designed with respect to one ISA are inapplicable to a processor with a different ISA.

Example of Effectiveness Limitation
Recall that the handtuned enemy processes showed an SUT slowdown of 1.14× and 1.36× for the Pi3 and 570X, respectively. These slowdowns were achieved by making reasonable human judgements for enemy process parameters. If instead, using the methodology described in the remainder of this paper, the enemy template is auto-tuned for two hours, different parameters can be found (summarised in Table 1).
The values found by auto-tuning do not correspond to any architectural features that we are aware of, and it seems unlikely that a human designer with detailed knowledge of these processors would guess them. However, the slowdowns of the SUT using this auto-tuned hostile environment increase to 10.08× 1 and 1.66× for the Pi3 and 570X, respectively. The slowdown results are summarised in Figure 2. Thus, an autotuning methodology for enemy processes (1) requires less architectural knowledge and manual effort than hand-tuned approaches of previous work, and (2) can provide greater slowdowns than reasonable hand-tuned enemy processes.
Contributions We present an auto-tuning methodology for hostile environments and the corresponding enemy processes. This approach can be employed for different chips, removing the need for the detailed architectural expertise required by prior works in this area. Additionally, because of the many configurations explored by the tuning approach, our methods may discover non-intuitive parameters that cause slowdowns beyond what might be developed through hand-optimisation, as illustrated in the above example.
We illustrate our approach by creating a hostile environment on five different chips for three interference paths: cache, memory bus, and the main memory system. This requires three enemy process, victim program pairs. For tuning, we explore three search strategies (random search, simulated annealing and Bayesian optimisation), and report on the effectiveness of each.
Finally, we assess the effectiveness of our approach at causing slowdowns in SUTs by running benchmarks from the coremark and autobench application suites [3,2] in the hostile environments produced by our tuning methodology. We show that we can achieve statistical significant slowdowns for 98% of benchmarks. We compare the slowdowns caused by our auto-tuned hostile environments with hand-tuned hostile environments from prior work and show that the slowdowns are comparable and that in some cases we are even able to achieve higher slowdowns.
In summary, our contributions, in order of presentation, are: 1. An auto-tuning methodology for hostile environments with the aim to cause slowdowns in an SUT; this methodology is portable and can be used to automatically tune hostile environments for different chips (Section 2). 2. An illustration of our methodology on five different chips for three interference paths: cache, memory bus, and main memory systems; we evaluate several natural search strategies and slowdown metrics (Section 3). 3. An assessment of the extent that our tuned hostile environments slowdown the coremark and autobench real-time application suites; we show that in many cases our auto-tuned hostile environments are as effective as hand-optimised assembly environments of prior work [22], and sometimes better (Section 4). The source code for our framework can be found online. 2

Creating a Hostile Environment
We now describe in detail our methodology for creating a hostile environment, which aims to be effective at causing slowdowns in an SUT through shared resource interference. After a high-level overview of our approach (Section 2.1), we detail the interference paths (shared resources) targeted in this work along with the associated per-resource victim programs (Section 2.2). We then explain how we use autotuning to search for enemy template parameters per resource (Section 2.3) and how hostile environments are constructed from tuned enemy processes (Section 2.4). Because different search strategies can be used in the tuning phase, we outline several natural choices (Section 2.5). We conclude the section by describing the care we have taken to ensure validity of the measurements that form the basis of the tuning process (Section 2.6).

Overview of our approach
The first step in our method consists of identifying possible interference paths, i.e. shared resources for which multi-core contention might cause slowdowns. For each one of these paths, we create: (1) a parameterised enemy template that will run in an infinite loop and stress the resource and (2) a victim program that performs a fixed amount of synthetic work, making heavy use of the resource. We tune the parameters of the enemy template using the slowdown it can cause on its corresponding victim program as the objective function. Because the victim program is vulnerable to interference on the target resource, the degree to which it is slowed down serves as a proxy for measuring interference on the associated interference path. Each enemy process is tuned to provoke interference to a specific resource. However, we want to develop a hostile environment that is effective at slowing down an arbitrary SUT. A black-box SUT is likely to use multiple resources in complex ways. Thus, we aim to find a combination of tuned enemy processes to be effective across all victim programs. The second step in our methodology involves searching for a combination of tuned enemy processes, with one enemy process running on each non-SUT core of the processor, that is effective in causing interference with respect to multiple resources. Table 2 summarises the parameters that can be used to configure our auto-tuning framework. In the tuning phase of our methodology, different search strategies can be employed.
We evaluate three natural choices: random search, simulated annealing and Bayesian optimisation (see Section 2.5 for more detailed descriptions of each), and compare their proficiency in finding effective parameters. Since measurements in this domain are noisy, we time multiple runs and analyse the results to best approximate the actual interference. These metrics, along with the steps we have taken to ensure measurement validity, are explained in more detail in Section 2.6.

Shared Resources and Victim Programs
We now discuss several shared resources that can lead to interference between independent applications running in parallel on separate cores. For each type of interference, we first outline the ANSI-C victim program we have designed to be vulnerable to this interference, and then describe a parameterized enemy template which aims to effectively provoke interference through this resource.
Bus Buses are used to transfer data between memory and processing elements. To reduce the area of a processor, the bus is often shared between multiple processing cores, requiring some form of arbitration mechanism. There are three main classes of resource arbitration mechanisms: (1) time-driven arbitration uses a predefined bus schedule that assigns time slots to contending components; (2) event-driven arbitration resolves contention at runtime, e.g. via a round-robin or first-infirst-out strategy; and (3) a hybrid approach that uses different runtime policies for each time slot [5]. Victim Program The bus victim program reads a series of numbers from a main memory data buffer and increments their value. The increment operation forces the enemy process to bring the numbers from main memory to the CPU. Afterwards, the process writes the numbers back to a second buffer in main memory. The buffers are allocated using malloc and are sufficiently large not to fit in the cache. This entire process ensures that the bus will be kept busy with transfers between main memory and registers. Enemy Template We have designed the enemy with the aim of hindering data transfers between the CPU and main memory. It achieves this by performing the same operation as the victim process and thus competes for the same bus resource. Each enemy template has distinct buffers to simplify the design and avoid using any synchronisation mechanism.
The configurable parameters are: • Size of main memory data buffers.
• Integer data type used for buffers : int8_t, int16_t • Which buffer is used for read and which for writes.
Intuitively, the larger the size of the data being transferred, the more contention it will cause. However, there might be patterns where fast transfers through the bus followed by pauses will trigger some timing issues as shown in [12].
Cache Caches are used to reduce latency between the processor and main memory. A cache is much smaller than the main memory and, as a result, only a small subset of main memory can be stored in the cache at one time. There are multiple cache levels, and usually the last level cache is shared between processor cores. As an example, the 570X has two levels of cache, where the level 1 data and instruction caches are core-local, while the level 2 cache is shared between all cores. Once a cache becomes full, stale data is evicted and replaced with newer data using a variety of policies, e.g., first in first out, least recently used, and random replacement [14]. Because these caching policies have a direct effect on memory latency, multi-core timing analysis must take into account potential interference from contending cores.

Victim Program
The victim program is configured based on the size of the shared cache, its associativity and its line size. The program allocates an array of the size of the last level cache and then reads and writes to the array in a pattern given by the associativity and the line size. The processor optimises access by storing the array in the last level cache.
Enemy Template The cache enemy template works by striding over a data buffer and performing a sequence of memory operations (reads or writes). The configurable parameters will force it to access the cache in a chaotic manner, thus working against data locality for which caches are designed to optimise. The cache enemy template is configured by the following parameters: • Data buffer size • Stride value • Series of operations; reads, writes (up to five) Main Memory Access to main memory is granted by a shared controller. In a single read or write operation, only one bank of memory can be accessed for single-channel memories and simultaneous requests can lead to delayed requests [26]. The interference issues are similar to those of shared caches, however, the runtime impact of contended memory accesses are higher.
Victim Program The victim program allocates a data buffer in main memory and writes random values to it. We ensure that the values are actually written in main memory and not just in the cache by having a sufficiently large buffer that does not fit in the cache for any processor.
Enemy Template The goal of the enemy template is to touch as many memory banks as possible. It allocates a large data buffer and repeatedly selects a random, contiguous sub-region of this buffer, of a given fixed size, and memsets the sub-region with random values. A randomly-selected byte is chosen and memset is used to write this byte across the region. The following parameters configure the enemy process: • The size of the data buffer • The size of the sub-region Overlap and Extensibility There will be a certain level of overlap between enemy processes. The hardware cache policies can make the memory enemy processes first write data to the cache and therefore also act as a cache enemy.
Also, we expect that the memory enemy process will stress the system bus by bringing data from the processor to the memory. However, we assume that each enemy process will concentrate its attack on one resource, e.g. the bus enemy, in contrast to the memory enemy, will only read data from the same locations in main memory and therefore will demand less work from the memory controller. This way the enemy processes will complement each other.
It is easy to extend the framework and stress different shared resources that we have not included and that might be unique to specific platforms. This extension can be done by adding a pair consisting of a victim program and an enemy template, including the tunable parameters.

Tuning enemy processes per Resource
For each processor, we tune the parameters of the enemy templates to cause the highest slowdown in their corresponding victim program. In this section, we describe how this process is performed.
In what follows, let R denote the set of resources to be targeted. In our current work, R = {bus, cache, RAM}. For a resource r ∈ R let V r denote the victim program associated with r, and T r the enemy template associated with r. For example V bus denotes the victim programs associated with the bus resource, and T cache the enemy template associated with the cache resource.
A template T r takes a set of parameters drawn from a parameter set P r . For a given parameter valuation p ∈ P r , let T r (p) denote the concrete enemy process obtained by instantiating T r with parameters p.
For a victim program V r , template T r and parameter setting p ∈ P r , let slowdown(V r , T r (p)) denote the slowdown associated with (1) executing V r in isolation on core 0 (with all other cores unoccupied), compared with (2) executing V r on core 0, in parallel with an instance of T r (p) on every other available core.
Our aim is to compute: Because P r is too large to search exhaustively, we use a search strategy to approximate the maximum. These are discussed in Section 2.5.
Let p tuned r denote the best parameter setting that was found via search using the chosen strategy. We refer to the set ResourceTunedEnemies = {T r (p tuned r ) | r ∈ R} as the set of resource-tuned enemies.

Tuning a Hostile Environment
The tuning process described in Section 2.3 considers the same enemy process running on every available core other than that occupied by the SUT. We aim to devise a deployment of enemy processes that is effective at inducing interference across all resource types since we do not know the resource usage profile of the SUT a priori. We determine the best configuration of enemy processes by using a strategy similar to the one described in [23].
We refer to a configuration of multiple possibly distinct enemy processes running on the non-SUT cores as a hostile environment. More formally, for an n-core processor where the SUT runs on core 0, a hostile environment is a mapping from {1, . . . , n − 1} → ResourceTunedEnemies.
We now describe our strategy for choosing a suitable hostile environment from the set of |R| n−1 possibilities. First, for each resource r ∈ R, we rank every possible hostile environment according to the extent to which they slow down V r , with the environment that induces the largest slowdown ranked first. Let RankedEnvironments(r) denote this ranking. This set is much smaller than the tuning set, so we can exhausting run these experiments. The most common processor in our case, a 4 core processor, with 3 shared resources would only require 81 evaluations.
We then select a Pareto optimal hostile environment. This is an environment e such that there does not exist an environment e = e such that for all r ∈ R, e is ranked more highly than e in RankedEnvironments(r). Being Pareto optimal, e may not be unique and in this case we use a tie breaking mechanism. The tie breaking mechanism consists of selecting the environment that is ranked better in all but one of the RankedEnvironments(r).

Search Strategies
To estimate the maximum interference caused to the victim program, we need to find effective parameters for the enemy templates given in Section 2.2. We intuitively expect the search space of enemy process configurations to be discontinuous with respect to interference, e.g. due to caches having fixed parameters that are typically powers of two, memory being organised in banks, etc. Therefore we utilise search strategies that do not make any explicit assumption about the convexity of the cost function and do not rely on gradient information. To do so, we evaluate the following candidates: Random search (RAN) RAN samples different configurations and remembers the best values. This approach has the advantage of being lightweight and providing a baseline for the more complicated techniques.
Simulated Annealing (SA) SA is a metaheuristic to approximate global optimisation in a large search space. It is often used when the search space is discrete (e.g., all tours that visit a given set of cities). For problems where finding an approximate global optimum is more important than finding a precise local optimum in a fixed amount of time, simulated annealing may be preferable.

Bayesian Optimisation (BO)
Having an unknown objective function, the Bayesian strategy is to treat it as a random function and place a prior over it. The prior captures our beliefs about the behaviour of the function. After gathering the func- tion evaluations, which are treated as data, the prior is updated to form the posterior distribution over the objective function. The posterior distribution, in turn, is used to construct an acquisition function that determines what the next query point should be.
There are advantages and disadvantages to each one of these strategies. Random search and simulated annealing can quickly determine the next query point. Bayesian optimisation needs time to remodel the objective function and the acquisition function after each new query is made. On the other hand, it is expected that Bayesian optimisation will only sample points that will increase our knowledge of the problem. In general, one would expect to prefer the first strategies in cases where the cost function is cheap to evaluate and BO in cases where the cost function is expensive to evaluate. We evaluate the effectiveness of these approaches in Section 3.2.

Measurement validity
A threat to the validity of our approach is that measurement errors and performance fluctuations due to external factors may cause us to wrongly conclude that our test harness is responsible for slowing down an SUT. Similar to other approaches that make use of enemy processes [11], we deal with factors related to the hardware, operating system, and compiler. We also make use of a statistical metric, more specifically quantiles, to refine our results.
Hardware Hardware mechanisms are generally designed to be transparent to the user but can be unpredictable. We took into account the following factors in our design: 1. Frequency throttling due to increased temperature. When the temperature of a processor increases beyond a limit, frequency throttling can kick in. Figure 3 shows how frequency is affected by temperature on the Pi3. The data was gathered using a tool called vcgencmd. We want to guard against the risk of attributing an SUT slow-down to interference caused by an enemy process when the slow-down is actually due to the raised temperature of the processor.
To mitigate this risk, we measure the temperature at the end of each experiment and discard the result if the temperature has risen above 80 degrees. We empirically found that using this temperature threshold works well on the other devices used. 2. Alternating between hot and cold caches. We flush the cache at the beginning of each experiment as data left from the previous experiment might affect the execution time of the next one.
Operating system Modern operating systems are multithreaded and include a range of elements, aimed at efficient execution of a large number of threads. However, this can make thread execution more unpredictable. We use the following techniques to mitigate the effects that the operating system could have on our measurements. 1. Thread migration. The operating system might decide to migrate the thread to a different core for various reasons. We avoid this by pinning the SUT and the enemy processes on a specific cores using the taskset linux command. 2. SUT preemption. To avoid the kernel from stochastically preempting the SUT, adding the cost of context switching to our measurement time, we run the application at the maximum possible priority. 3. Ensure parallel execution. The operating system might decide to postpone the startup of any of the enemy processes after the SUT has started, rendering the experiment practically useless. To evaluate the maximum startup latency in different platforms that we considered, we used the latency evaluation framework [4] and discovered that the maximum startup latency is generally under 1 ms. To ensure that all the enemy processes are running before the SUT starts executing, we wait 10 ms after all the enemy processes have started, which should be a conservative margin for any evaluation. 4. Remove unnecessary software The interaction between different software can be difficult to predict. To mitigate the chance of this occurring, we removed all software that is not strictly required for our experiments such as any graphical capabilities, logging software and network managers.
Compiler The compiler might optimise away part of the code in the enemy process to reduce execution time, decreasing the stress it is intended to put on specific resources. To avoid this, we generate random numbers at runtime for certain elements, e.g. the number written by memset. Furthermore, we run the compiler with the -O0 flag.
Statistical analysis We can never truly get rid of all nondeterministic elements of our environment that can interfere with our measurements. For this reason, we need to measure multiple runs and apply a statistical analysis.
We need a metric that can reliably estimate the worst-case execution time and ignore the unreliable outliers. Oliveira et. al. [9] show how quantiles can be used to compare the latency and end-to-end times of two different Linux schedulers. We follow a similar approach and run the same experiment multiple times and calculate the quantile. Since we are interested in the worst-case behaviour, we would naturally want to select a quantile that is close to the 100 th one as possible. However, choosing too high of a quantile will not properly disqualify outliers. Figure 4 shows the variance of different quantiles for our development platforms. For each board, we ran the coremark benchmark alongside some manually tuned enemy processes and measured the execution time 1000 times. We divided this execution times into 40 sets of 250 data points each and we measured the quantile from the 75 th to the 100 th . Afterwards we calculated the variance of the 40 sets for each development board and plotted the results. The figure shows how selecting too high a quantile results in noisy data. For that reason, we have chosen the 90 th quantile as our slowdown metric.
Another issue that we take into consideration is the number of measurements required for obtaining a reliable value of the quantile. For this reason, we measure 20 times with the same configuration and calculate the 90% confidence interval. If the range of values within the interval is too high, we repeat the process and add more measurements until it decreases to a desired threshold. We do this by calculating the difference between the quantile and the interval endpoints and checking that it is not higher than 5%. However, it often happens that the measurements never converge to the desired threshold. For this reason, we limit the number of measurements to 200.

Experimental setup
We now evaluate the effectiveness of our approach by running it on a collection of embedded development boards, and evaluating its effect on a series of industry benchmarks common in time-constrained domains. In Section 3.1 we present the utilised hardware and afterwards in Section 3.2 we compare the considered search strategies. We conclude this part with Section 3.3 where we describe how we developed a hostile environment for each platform.

Hardware and Benchmarks
Benchmarks The synthetic victim programs we created are designed to be especially vulnerable to shared resource interference. While these synthetic applications show how we can achieve extreme interference for a specific resource, we are also interested in observing the effects on industry standard benchmarks. These benchmarks are summarised in Table 3. [3] is a standardised benchmark used for evaluating processors. It is composed of implementations of the following algorithms: list processing (find and sort), ma-   trix manipulation (common matrix operations), state machine (determine if an input stream contains valid numbers), and CRC (cyclic redundancy check). EEMBC Autobench2 [2] consists of automotive workloads, including: road speed calculation and finite impulse response filters. This benchmark suite is of interest for the real-time industry and has been used in the evaluation of other works in this domain, e.g. [13,12].

EEMBC Coremark
Hardware We have chosen a range of development boards, containing both ARM and x86 CPUs to evaluate the portability of our approach. Table 4 shows the SoC, the architecture and the number of cores for each of them.
The operating system can have an impact on the effectiveness of our approach. This impact is minimised to create a fair comparison between the different development boards. We used Debian Linux, as it was available across all platforms.

Comparing Search Strategies
We now compare the search strategies described in Section 2.5 and determine which one is the most proficient at finding effective parameters of the enemy templates. We tune the enemy templates using their corresponding victim program as described in Section 2.3 with all three search strategies tuning for 2 hours. Since all search strategies have a certain degree of randomness and can sometimes get lucky or unlucky (even BO starts by randomly sampling its starting points) we perform three runs of each search method for each shared resource. We use the Wilcoxon rank-sum method to test if values from one set are stochastically more likely to be greater than values from another set. This method is non-parametric, i.e. it does not assume any distribution of values, and returns a p-value indicating the confidence of the result. The results of this experiment can be found in Table 5 where we constructed an order of the effectiveness of each search method. However, some orders are more confident than others, i.e. the ones with a low enough p-value. When the p-value is low (below 0.5) we have higher confidence in the ordering, denoted by the < symbol. However, when the p-value is high (above 0.5) we are not as confident in the ordering, denoted by the ≈ symbol. In all cases SA seems to perform the worst. BO performs well for the memory enemy process and RAN for the bus enemy process. However, the difference is not clear for the cache enemy process, with RAN and BO randomly obtaining the best result. RAN performs well due to the highly irregular search space that the parameters of our enemy templates have. BO can intelligently sample points of interest quickly and has reduced chances of getting stuck in a local minimum. It is surprising that SA ranks last in our comparison. Most likely the search space is highly irregular, and the algorithm often gets stuck in a local minimum. Of course, SA can be configured to focus more on exploration, but then there would be no reason to use it in place of RAN.

Creating a Hostile Environment
After determining the appropriate search strategy for each of the enemy processes on each board, we search for the most aggressive parameters that maximise interference. We tune each of the enemy templates and its corresponding victim program with the winning strategies for a more extended period (8 hours). Table 6 presents the maximum slowdown obtained alongside the search strategy used. The most significant slowdowns were obtained for the cache or main memory resources. The bus appears less vulnerable to interference than the other two resources.  From Table 4 we see that the Pi3 and the 410c have the same architecture, but implemented in different SoCs. The Pi3 is especially vulnerable to cache interference while the 410c is much less prone to the same type of interference. It is likely that this can be explained by microarchitectural differences between the the two boards; however, we are not aware of the exact mechanism that causes this difference as low-level details are generally not available for most SoCs on the market today.
We now determine the optimal hostile environment for each of the boards using the methodology described in Section 2.4. An example of this approach can be seen in Table 7, where we have the ranked list of each of the possible environments for the 570X. For this platform, the MBM configuration is the Pareto optimal, where MBM denotes the hostile environment where the first core stresses the Main memory, the second core stresses the Bus and the last one also stresses the Main memory.

Results
Now we evaluate the effectiveness of the hostile environment on the benchmarks of Section 3.1. In Section 4.1 we measure how the benchmark runtimes are influenced by our hostile environments and then compare our method with previous approaches in Section 4.2.

Evaluating the Hostile Environment
With the Pareto optimal hostile environment determined for each SoC, we evaluate its effectiveness on the benchmarks described in Section 3.1. Figure 5 shows the results of the hostile environment on the benchmarks for each one of the boards. To determine if our slowdowns are statistically significant, we calculated the 90% confidence interval for the benchmarks running in isolation compared to running in the hostile environment. We then proceed to determine if there is any overlap between the two. There are only two cases when they overlap, that is for benchmark a on the 410c and for benchmark f on the M3. For the considered benchmarks, we can summarise that this approach has a 98% effectiveness of slowing down applications across the benchmark suite we consider. Figure 5f shows the geometric mean of all benchmark slowdowns for each of the platforms. The Pi3 is the most vulnerable development board in our experiments, while the 410c is the most resilient one. This score does not provide a hard guarantee of the timing predictability of any of the tested boards, but it does offer a means by which we can quickly eliminate unreliable platforms. This experiment is in line with the results from Table 6 where only the victim processes were used, and each resource was stressed individually. More specifically, the large slowdowns in the victim programs can be directly correlated to the large slowdowns in the benchmarks.

Comparing with Hand-Written Assembly
Previous approaches have relied on hand-crafting assembly enemy processes to assess the maximum slowdown that a given platform can experience. One example of such an approach involves implementing a pointer chasing scenario [22], in which the enemy process creates a large array of addresses in main memory where each address points to a different location in the same array. The enemy process then starts to navigate the array using assembly code. By utilising assembly code, the compiler is prevented from performing any optimisations. The irregular access pattern contravenes the locality principal needed by the cache to store information efficiently.
Using the code provided by the authors of [22], we evaluated our hostile environment against the previous approach. Since this code is written to assembly language we were only able to execute it on the 570X, which is the only x86 development at our disposal. We ran the benchmarks alongside the hand-crafted assembly enemy processes and measured the slowdown. Figure 6 shows the slowdowns observed using the hostile environment and the hand-crafted assembly enemy processes with two different array sizes (4KB and 4MB). We calculated the 90% confidence interval for the obtained results to evaluate if the differences between the two approaches are of statistical significance. The confidence intervals proved to be relatively large, and there was an overlap between the two methods. In 14 cases out of the total 21 benchmarks, our method achieves a higher slowdown. However, out of those,  Figure 6: Comparison with two hand-written assembly enemy processes on the 570X. The first assembly enemy process uses an array of size 4KB to store addresses, while the second one uses an array of size 4MB.
only 11 of them are statistically significant. In the other 7 cases, the assembly approach can reach a higher slowdown, but the confidence intervals always overlap. Our experiments show a statistically significant higher slowdown in 52% of the applications, in the remaining 48% of the cases, there was no statistically significant difference. While our method does not always outperform the hand-written tests, our method has the advantage of being portable, i.e. it does not require crafting assembly code for each specific platform.

Related work
Applications with similar functionality as the enemy processes have been used before in the literature. They have been referred to as resource stressing benchmarks [22], resource stressing kernels [12] or synthetic contenders [1]. Radojkovic et al. [22] were the first to utilise such techniques by deploying assembly code to measure multi-core interference on real application workloads. They propose a framework for quantifying the maximum slowdown obtained during simultaneous execution by stressing a single shared resource at a time. Their work examines several Intel processors, exploring the extent that the interference from resource stressing benchmarks can slow down real-time software. Nowotsch et al. [19] perform a similar experiment on a multi-core PowerPC-based processor platform and focus specifically on the memory system. The platform allows for different memory configurations and provides several methods for interference mitigation. Regardless of configuration, SUT slowdowns are still observed when executing the resource stressing kernels on distinct cores. Fernandez et al. [13] evaluate a multi-core LEON-based processor and run experiments with both a Linux and real-time operating system. Unsurprisingly, the slowdown is mitigated on the real-time operating system, but not eliminated.
Fernandez et al. argue that most resource stressing benchmarks may fail at producing safe bounds [12]. Under heavy contention, arbitration policies of shared resources such as round robin and first in first out produce a so-called "synchrony effect" that causes the SUT to suffer a delay that is not as severe as the potential worst-case. They propose a method to improve the bound by varying the injection time between requests to the shared hardware resources. Approaches such as [16,10] rely on randomisation of the source code to produce different memory mapping and therefore gather a large set of possible execution times. They utilise a statistical approach called "extreme value theory" and can provide multiple worst-case execution times alongside a confidence factor.
Tuning strategies have been used to optimise different computational aspects, with Ansel et al. [6] showing how such an approach can be used for a variety of domain-specific issues. Wegner et al. [25] use genetic algorithms to find the inputs that cause the longest or shortest execution time. To do so, they formulate the search for such inputs as an optimisation problem. Law et al. [17] use simulated annealing on a single core processor to maximise code coverage and therefore obtain an estimate of the WCET.
Griffin et al. [1] take a different approach and train a deep linear neural network to learn the relationship between interference and the effect of the SUT execution time. This approach is used to calculate an interference multiplier that can be applied to a previously calculated WCET without interference.
Previous approaches are limited by the need of the developers to tune each resource stressing benchmark for each specific SoC and also can not always detect hidden interference patterns that are specific to the underlying microarchitecture of the system. In contrast, our approach assumes no knowledge of the architecture and microarchitecture of the system and can detect hidden interference patterns automatically.

Conclusions
We have devised a portable auto-tuning method for determining interference across a wide range of platforms. Our approach is based on configurable enemy processes and does not rely on advanced knowledge of the microarchitectural details of a given platform. For determining the more effective parameters, we compared three different search strategies and determined the better candidates.
We evaluated this method across a wide range of processors, consisting of both ARM and x86 processors using industry standard benchmarks. Our approach is capable of causing interference in 98% of the cases. We compared the slowdowns caused by our auto-tuned hostile environments with handtuned hostile environments from prior work and showed that the slowdowns are comparable, and in some cases, even able to achieve statistically significant higher slowdowns.