Base Header Level:  3
latex input:        document-info
latex input:        x3-util
latex footer:       x3-paper-end

<!--\clearpage-->
# Introduction and Motivation

Embedded platforms and applications are being used throughout many industries, including telecommunication, robotics, medical and automotive <!--\citep{Wang2013}-->. <!--\citet{Campeanu2013}--> states that nowadays it is increasingly common to build embedded systems on heterogenous platforms, containing multiple computational units such as multi-core CPUs, GPUs and FPGAs. If all these components are used to the full extent of their capabilities, such a setup enables better performance than could be previously achieved on embedded platforms.

This is an important shift, since applications running on these platforms become more and more complex, and their performance requirements have to be satisfied. In the past, not only on embedded platforms, this was commonly achieved by boosting processor clock rates. But due to both intractable physical limitations as well as practical engineering considerations and requirements (i.e. low power consumption for a battery-powered devices), increasing processor clock rates is no longer a feasible approach <!--\citep{Cantrill2008}-->.

The performance gains possible on heterogenous embedded platforms are a result of the hardware-level parallelism that is achievable on these platforms. But unfortunately, exploiting these parallelism features still is a considerable challenge, given the tools and programming concepts widely in use today <!--\citep{Wang2013,Cantrill2008}-->.

Because of the mentioned increase in complexity and performance requirements in embedded applications, and considering that clock rates can't be expected to increase significantly anymore, research in the area of concurrent applications has taken an important place in the computer science research community. As a result, an increasing number of publications related to concurrency have been presented over the last 7 years (compare <!--\autoref{fig:concurrency-publications}-->).

<!--\fig{concurrency-publications}{A strong increase in publications related to concurrency can be observed over the last years. These numbers were researched from the \citet{IEEEXplore2013} digital library as well as the \citet{ACMDigitalLibrary2013} digital library.}-->


In context of the <!--\citet{FAUST2013}--> at the Hamburg University of Applied Sciences, a specific embedded application in need of parallelization is an obstacle detection system in cooperation with a lane guiding control, for which an overview is given in <!--\autoref{platform}-->. Evaluating the distance data measured by a laser scanner and the image stream providing extracted road marking parameters constitues a dataflow parallelization problem, for which suitable algorithms must be researched and compared. Therefore, this essay focuses on the literature evaluation of techniques that allow for the exploitation of potential sources of parallelism, especially those which are suitable for dataflow problems (<!--\autoref{techniques}-->). Said evaluation is done in context of a modern multiprocessor system-on-chip platform (MPSoC), the Xilinx Zynq 7000.

Additionally, the current lack of widely used tools and languages crafted for use in concurrent environments is addressed by giving a short overview of the "Go" programming language (<!--\autoref{golang}-->). It was conceived, among other things, to allow for a highly productive development environment for parallel applications. The hypothesis is thus, that by using the Go programming language to develop parallel applications on embedded platforms, one can increase developer productiveness by allowing the language to take care of tasks such as thread scheduling and memory management. Given that said features come at the cost of performance, their impact on embedded systems must be evaluated (<!--\autoref{go-performance}-->).

Given that the FAUST project focuses on MPSoC platforms for implementation, the evaluation is confined to the area of Symmetric Multiprocessor (SMP) systems, i.e. those having multiple processors or processing cores with shared memory. Other parallelism concepts for architectures such as Distributed Memory Access or Nonuniform Memory Access will not be discussed.




# Platform and problem description [platform]

This section covers a short overview of the platform currently being used for embedded application development in the <!--\citet{FAUST2013}-->, the Xilinx Zynq 7000, along with the applications running on that platform (visualized in <!--\autoref{fig:zynq-platform}-->). This overview is given to establish a context for the subsequent sections, and to give an idea of recent work. At present, there are two major subsystems that need to run in parallel on this platform, which are part of a technology field trial of advanced driver assistance systems:

1. An obstacle detection system (<!--\autoref{obstacle-detection}-->)
1. A lane guiding control system (<!--\autoref{lane-guiding-control}-->)

<!--\fig{zynq-platform}{Overview of the platform being used, as well as its subsystems.}-->

The Xilinx Zynq 7000 architecture is composed of a dual-core ARM Cortex-A9 CPU and an on-chip programmable logic unit (FPGA). The ARM Cortex-A9 additionally contains the ARM NEON general-purpose SIMD engine <!--\citep{Xilinx2013}-->, for which an overview will be given in <!--\autoref{arm-neon}-->.



## Obstacle detection system [obstacle-detection]

This laserscanner-based obstacle detection system was realized by <!--\citet{Jestel2013}-->, providing an implementation in the Java programming language running on the Android 4 operating system. Because of the fact that the Zynq 7000 platform was not yet available when development on the system started, it is currently running on the "Open Multimedia Application Platform 4430" (OMAP4), which features similar specifications to the Zynq platform, most importantly a dual-core Cortex-A9 CPU, but lacks the integration of an FPGA.

In his implementation, <!--\citet{Jestel2013}--> employs two worker threads assigned to each of the CPU cores, and sets them to maximum priority for the operating system scheduler. Each of the worker threads alternate at receiving and evaluating the data received from the laserscanner. The synchronisation is realized through software mutexes (compare <!--\autoref{fig:jestel-threading}-->).

<!--\fig{jestel-threading}{Parallelization concept as used by \citet{Jestel2013} in his obstacle detection system implementation.}-->



## Lane guiding control system [lane-guiding-control]

The previously developed System-on-Chip Autonomous Vehicle architecture devised by <!--\citet{Johannsen2013}--> was ported to the Zynq 7000 platform by <!--\citet{Andresen2013}-->. When porting the architecture he focused on distribution of computational tasks between the available CPU cores and the programmable logic (FPGA), thus making optimal use of the available resources in order to accommodate additional subsystems in the future (such as the obstacle detection system).

The resulting system uses the FPGA for processing of the raw image data received from a 60 FPS camera with a resolution of 640x480px. The output data from the FPGA is then evaluated by one of the available CPU cores, which then calculates the steering parameters to keep the vehicle driving in its lane. The other core manages the computations executed on the FPGA plus any operating system tasks that may arise.

The developed system runs on a Linux Kernel v3.2, and it should be noted that the system's timing constraints for the lane guidance were kept despite the fact that no real-time patch (e.g. `CONFIG PREEMPT RT`) had been applied to the kernel (the scheduler was set to `SCHED FIFO`). It is reasonable to assume that this was possible due to the relatively low framerate and resolution of the camera, which resulted in an low overall system load. With increasing load, however, it might not be possible to keep timing constraints without either applying a real-time patch to the kernel or using a real-time operating system (RTOS).




# Parallel computing architectures

A system for classification of parallel computing architectures is outlined in this section, to provide context for the subsequently explained parallelism techniques. <!--\citet{Flynn1972}-->, in what is often referred to as "Flynn's Taxonomy", states that there are four broad categories of computer architectures:

<!--
\begin{tabular}{ll}
\textbf{SISD} & Single instruction, single data\\
\textbf{MISD} & Multiple instruction, single data\\
\textbf{SIMD} & Single instruction, multiple data\\
\textbf{MIMD} & Multiple instruction, multiple data\\
\end{tabular}
-->

SISD is what can be found in most single-core computers, where a single stream of input data is processed by a single stream of instructions. SISD is not further discussed since it has no parallel elements, and MISD is completely ommited because there are no well-known systems that fit into this category <!--\citep{Mattson2004}-->.

In this essay, the focus is on SIMD and MIMD architectures, which offer a potentially high degree of parallel execution.



## Multiple Instruction, Multiple Data

The MIMD category in Flynn's taxonmy, as depicted in <!--\autoref{fig:MIMD}-->, features multiple control-units which provide instructions to a number of processors, each with their own data input stream. Additionally, the MIMD processing units may be interconnected. This category is the most general, and applies to all multi-processor system currently available <!--\citep{Mattson2004}-->.

<!--\figw{MIMD}{Visualization of a Multiple instruction, multiple data architecture. There are three different processing units, each featuring its own control unit and processor. All processing units have disjoint instruction and input data streams. The processors may be interconnected among each other.}-->



## Single Instruction, Multiple Data

Given a single stream of instructions and multiple streams of data, an SIMD architecture is one that applies each instruction to all of the data streams **in parallel**. This process is visualized in <!--\autoref{fig:SIMD}-->, where there is a single control unit providing the processors with instructions. For every instruction, every processor takes an item out of its own input stream and applies the instruction.

As such, SIMD architectures are a good fit for specialized applications that consist of high data parallelism along with little interprocess communication. As an example, in digital signal processing, e.g. decoding a video stream, the situation is common that the same operations need to be applied continously to a large stream of data (in case of a 1080p high-quality H.264 movie, the bitrate is usually above 17Mbit/s). As such, SIMD instruction sets have become a generic feature of most high-end processors because of the possible performance improvements that they can provide <!--\citep{Jang2011}-->.

A Graphics Processing Unit (GPU) can also be seen as a heavily parallel SIMD system. GPUs usually consist of an array of multiprocessors, each in turn consisting of multiple processing units (ALUs), which all share a single control unit (exactly as depicted in <!--\autoref{fig:SIMD}-->). Each of these ALUs is then able to execute instructions provided by the control unit, potentially providing a many-fold performance increase.

<!--\fig{SIMD}{Visualization of a Single instruction, multiple data architecture, containing three processing units. Heavily inspired by \citet{Mattson2004}}-->




# Parallelization Techniques [techniques]

In this section, selected techniques will be explained in context of the concrete problem at hand, that is the parallelization of the obstacle detection system on an ARM Cortex-A9 CPU. Since there are a multitude of techniques, and not enough time available to evaluate all of them, the selection was mostly based on the information provided by <!--\citet{Mattson2004}-->. If possible, current research based on these techniques will be highlighted. <!--\todo{Perhaps say something about exploitable concurrency}-->

## ARM NEON SIMD [arm-neon]

The ARM NEON extension is a general-purpose 128-bit SIMD architecture available in ARMv7. It is included in the ARM Cortex-A9 processors that can be found on the Zynq 7000 <!--\citep{Jang2011}-->.

The NEON extension is composed of 32 64-bit registers, which can alternatively be used as 16 128-bit registers. In NEON terminology, a register is a vector of elements of the same data-type (possible data-types are 8-bit to 64-bit signed and unsigned integers as well as single precision floats). Every instruction given to the NEON engine is then performed on all elements of one or more such vectors. A "lane" is the part of all registers which shares the same operation. This process is visualized in <!--\autoref{fig:NEON-SIMD}-->.

<!--\fig{NEON-SIMD}{Visualization of the NEON SIMD data flow. Assuming 64-bit source registers, each of the operands is 16-bit wide. The blue squares represent the operations applied to the registers. Four operations are applied, and thus there are four lanes.}-->

<!--\todo{Add a few standard operations, and maybe a short example}-->Even though the NEON engine is realized as part of the ARM core, it has a completely independent execution pipeline and a register bank that is separate from the ARM core register bank <!--\citep{Jang2011}-->. As a result, it is possible to signal the ARM core memory system to pre-load memory (using the `PLD` instruction, see <!--\citet{ARMAssembler2013}-->) while the SIMD engine is computing, so that the data for the next SIMD operation will already be available in the cache. This is important since cache misses can impose a significant performance overhead <!--\citep{Zang2013}-->, because the processor can not perform useful operations while loading memory into the cache.

A performance comparison conducted by <!--\citet{AlchemyLab2013}--> (not associated with ARM Ltd.) shows that, in case of the NEON engine, pre-loading subsequently needed data into the caches while performing SIMD operations can increase performance by a factor of 2.4 for an example algorithm containing multiplication, addition and divison with rounding (data size about 4MiB). It should be noted that this is the performance increase compared to an implementation that already uses the NEON engine. When comparing the NEON implementation with pre-loading to an implementation in pure C, the performance increase factor is 11.946 (compare <!--\autoref{fig:NEON-benchmark}-->). The algorithm, which does not serve a particular purpose other than benchmarking, is shown in <!--\autoref{code:simd-bench}-->.

<!--\includec{simd-bench.c}{simd-bench}{Algorithm specifically crafted for a performance comparison between C code and the NEON engine. Source: \citet{AlchemyLab2013}}-->
<!--\fig{NEON-benchmark}{Results of a performance comparison conducted by \citet{AlchemyLab2013} (not associated with ARM Ltd.).}-->

This technique will be very useful when parallelizing the obstacle detection system on the Cortex-A9, since processing the raw data from the laser scanner in preparation for the obstacle detection consitutes a dataflow.



## Pipeline Pattern [pipeline-pattern]

The pipeline pattern is a technique that can be used to  parallelize problems composed of a sequence of operations which have to be applied to a sequence of data elements, also called a "dataflow problem" <!--\citep{Mattson2004}-->. Pipelining techniques are widely used throughout the computing industry: at a very low level, virtually every modern microprocessors is equipped with an instruction pipeline to increase performance <!--\citep{Finlayson2013}-->. Higher up in the abstraction hierarchy, UNIX applications and shell scripts often rely on "pipes" as an interprocess communication mechanism. When considering application development, the pipeline pattern is often used because it allows the transformation of a sequential problem to a parallel solution with relatively low effort, especially without a full rewrite of a codebase <!--\citep{PreudHomme2012}-->. The pipeline pattern is a MIMD technique.

An example of a dataflow problem is the application of a sequence of image filters to a sequence of source images (like the one depicted in <!--\autoref{fig:image-pipeline}-->). While it isn't usually possible to apply multiple of these image filters to the same image at the same time, because they requires the output from the previous filter, each of them can be applied to **different images** concurrently.

<!--\fig{image-pipeline}{Example of a sequence of image filters. These filters need to be applied sequentally to every image in order to get the correct result.}-->

The pipelining technique then consists of dividing these sequential operations (e.g. image filters) into several pipeline stages. Each of the pipeline stages only ever processes a single data element (e.g. image) at the same time, but all pipeline stages work on separate images concurrently, as shown in <!--\autoref{fig:pipeline}-->. This requires the pipeline stages to be interconnected, since every stage must pass on the computed data to the next stage and receive the next data to compute from the previous stage. The implementation of a pipeline stage always adheres to the same structure:

1. Receive data from the previous stage
1. Perform operations on the received data
1. Pass the data on to the next stage

In order for a pipelined computation to have positive performance impact, some important constraints must be kept:

1. The time it takes to compute each stage's result must be much higher than the time it takes to pass the data on to the next stage. If this is not the case, the communication overhead introduced by the pipeline stages will outweigh the benefit of computing the stages concurrently <!--\citep{Mattson2004}-->.

1. Optimally, each stage takes about the same time to compute. If any pipeline stage completes its calculation much faster, it will be stalled by the subsequent stages, because it cannot pass on the computed data. On the other hand, if any pipeline stage is much slower than the others, subsequent stages will often have to wait for data to arrive, thus wasting computing time.

It remains to be evaluated whether or not the pipeline pattern can be used further parallelize the obstacle detection system, since it is not clear yet if the problem can be divided in stages such that the previously stated rules apply.

<!--\fig{pipeline}{A four stage pipeline processing four data elements.
In the first time frame, only the first pipeline stage is active, processing $\operatorname{Data}_1$ (all other stages are idle). In the second time frame, $\operatorname{Data}_2$ is being processed in stage one, $\operatorname{Data}_1$ in stage two, and so on.}-->

Recent research surrounding the pipeline pattern includes the concept of "pipelets" by <!--\citet{Jahn2013}-->. It describes pipeline stages which are self-organizing, by monitoring computational demands and communication time, and dynamically remapping tasks using the resulting information. As such, pipelets are able to group tasks such that the communication overhead is minimized and bottlenecks are reduced.

Since the pipeline pattern is very well applicable to the concurrency tools built into the Go programming language, a short example implementation of the pattern in Go will be presented in the next section.




# The "Go" programming language [golang]

This section will give a short overview of the programming language "Go", as well as provide a comparison to C, since it is the most common programming language used on embedded platforms <!--\citep{Douglass2011}-->. To wrap up this section, an example implementation of the pipeline pattern will be presented and explained.

Go is a general-purpose programming language first released in 2009, and currently being developed as an open-source project <!--\citep{Golang2013}-->. Its main goal is to increase developer productivity, especially when working in environments featuring multicore processors, networked systems and massive computation clusters (as such, it tries to solve MIMD problems). It also aspires to be easy to learn. An additional focus when developing the language was the reduction of compile time, since some modern languages, like C++, suffer from very high compilation times for larger projects <!--\citep{DAngelo2012}-->, which in turn reduces developer efficiency.

<!--\citet{Pike2012}--> states that most of the languages widely in use today were created in environments that are, in large parts, unrelated to the current computing landscape. He is referring to the paradigm shift from sequential to concurrent applications, where it becomes necessary to reason about and implement concurrent software in order to exploit the available resources. Languages such as C/C++, Java and Python were built when the importance of multi-processor programming had not yet developed, and as such, all of the parallelism concepts that can be found in them are the result of the adjustment of the language to requirements that developed after the language was first released. As an example, the building blocks for concurrent applications that can be found in the `java.util.concurrent` package were introduced in Java version 1.5, eight years after the initial language release <!--\citep{Peierls2005}-->. As a result, writing concurrent applications in these languages can often feel like a workaround <!--\citep{Pike2012}-->.

Go in turn was developed with concurrency as one of the main language features. The compiler inserts a small runtime environment into every compiled binary, which, apart from providing convenient features such as garbage collection, is able to distribute the so-called "goroutines" onto a pool of operating system threads. As a result, goroutines are much more "lightweight" than OS threads, since no interaction with the operating system is necessary to create and schedule them (other than the management of the thread pool). Creating a goroutine does not require additional work for the developer compared to calling a function, as can be seen in <!--\autoref{code:goroutine}-->.

<!--
\includego{goroutine.go}{goroutine}{Example of the sequential invocation of a function compared to the concurrent execution using a goroutine.}
-->

The communication between goroutines is realized through channels (the language keyword being `chan`), which have a capacity defining how many messages each channel can hold. Go's communication model can thus be regarded as a "message-passing" model. If any goroutine tries to insert or retrieve data from a channel, and if this channel is full or empty respectively, then the invoking goroutine will block until data becomes available. As a result, channels are also the synchronization primitive in Go.

<!--
\includego{channels.go}{gochannels}{Syntactical overview of channel usage in Go.}
-->

The language includes additional features, which do not deal with concurrency, but have been added for developer convenience. Some of these features include multiple return parameters, slices (which provide a more powerful interface to C-style arrays), garbage collection (which will be discussed in the comparison to C) and built-in dependency management.



## Comparison to C [go-performance]

This section highlights the most important differences between Go and C, where the importance is measured against the potential impact a difference may have when developing embedded applications.

The most important difference between Go and C is Go's usage of garbage collector. In C, memory must be managed by the programmer, which is an important property for embedded systems, since memory is often a very limited resource there. For the Android operating system, which mostly runs on battery powered embedded platforms (smartphones and tablets), the user-facing software ("apps") is written in Java. Since Java is a garbage collected language, the DalvikVM executing the Java bytecode also features a garbage collector. The fact that such a widely used platform uses garbage collection does not imply that it is a good fit for embedded platforms, though. In a recent article, <!--\citet{Crawford2013}--> analyzes the impact of garbage collection on mobile platforms, coming to the conclusion that garbage collection performs well as long as the available memory exceeds the needed memory by a factor of 6 or more. "In a memory constrained environment garbage collection performance degrades exponentially". As a result, the impact of Go's garbage collector on application performance will have to be closely evaluated.

Another important difference is the existence of goroutines. In C, in order to create a concurrent execution path, an operating system thread must be created. Go on the other hand creates a fixed pool of operating system threads, onto which created goroutines are mapped dynamically at runtime. This is achieved through Go's own scheduler, which is embedded into every compiled application. Having two separate schedulers (from the operating system and from Go) may have adverse effects on performance, since both schedulers are independent. While it would be possible to assign the highest operating system scheduling priority to the Go process (similar to the approach by <!--\citet{Jestel2013}-->), it would still be difficult to predict the timing behavior of a Go application. Accordingly, Go cannot be used for hard real-time applications in its current state, and its applicability for a soft real-time environment remains to be determined.

At this time it is unfortunately not yet possible to present proper results of a performance comparison between the two languages. This is due to the fact that benchmarking in general is a "notoriously delicate" task. It becomes even more complicated when one is trying to perform so-called micro-benchmarks, where the performance of two short piece of code (as opposed to two applications) written in different languages is to be compared <!--\citep{Gil2011}-->. One of the reasons for this complexity is that it is inherently complicated to write two pieces of code in two different languages, such that both code pieces do exactly the same thing in the most performant way possible. Writing such code requires advanced knowledge of the language, as for example choosing the wrong datastructure in one of the code pieces may result in completely misleading benchmark results, from which false conclusions will be drawn.

To summarize it can be said that, while Go is a compiled and statically typed language that has the potential to reach near-C performance, especially the scheduler and memory management model can lead to performance issues when running on embedded hardware. Whether or not Go can be productively used on embedded platforms warrants extensive practical research.



## Example: Pipeline Pattern in Go [go-implementation]

In this example, an implementation of a pipeline in Go will be shown. The pipeline pattern was chosen because it is likely that it will be used for the parallelization of the obstacle detection system. While the pipeline pattern is especially suited for implementation in Go, other popular patterns are also idiomatically implementable in Go, and are usually shorter or at least have the same length then an equivalent implementation in Java or C++ <!--\citep{Schmager2010}-->. For the sake of brevity, the operations which the pipeline performs are very simple: it first multiplies every input by two (Stage #1) and then prints the value to the console (Stage #2). The implementation consists of two parts: the initializer (<!--\autoref{code:go-pipeline-initializer}-->) and the stages (<!--\autoref{code:go-pipeline-stages}-->).

<!--\includego{go-pipeline-initializer.go}{go-pipeline-initializer}{The pipeline initializer, which creates the necessary channels, fills the pipeline with some elements and invokes the stages.}-->

The initializer first creates the necessary channels for the pipeline, by invoking `make()` with the type "`chan int`" (lines 4-6). The pipeline only needs three channels, since the first stage's output channel is also the second stage's input channel. The initializer then populates the first stage's input channel with values (ommited in <!--\autoref{code:go-pipeline-initializer}-->), and invokes both pipeline stage functions as goroutines (line 13+14), so that they execute concurrenly. Lastly, the initializer waits until all values have been processed by the second stage (line 17-19).

<!--\includego{go-pipeline-stages.go}{go-pipeline-stages}{The first pipeline stage multiplies every element by two, the second pipeline stage prints the elements to the console.}-->

The stages are a perfect match for the pipeline stage structure previously described in <!--\autoref{pipeline-pattern}-->. When looking at the first stage, the first line is the function signature, taking the input and output channel as paramters. In Go, a for-loop such as "`for { }`", that is without parameters, executes an infinite loop. As such, the pipeline stages reads data from the input channel into the "`current`" variable until the program terminates (line 3). Since reading from an empty channel is a blocking operations, the loop does not perform busy-waiting, which would waste many CPU cycles. On line 4 the operation on the current element is performed, and on line 5 the processed data is written to the output channel of the stage.

Importantly, the shown pipeline would not have any performance benefit, since the time it takes to communicate between the channels is much larger than the operations within the stages (the multiplication and the writing to the standard output). However, it would be very straight forward to replace the simply operations shown in the example by more complex operations <!--\todo{This still sounds a bit incomplete}-->.


<!--\clearpage-->
# Summary



<!--\todoin{Komprimierte Darstellung der wesentlichen Inhalte}-->

<!--\todoin{Ausblick (wie geht es weiter?)}-->

<!--\todoin{Schwarz: Software Engineering for Embedded Systems: Methods, Practical Techniques, and Applications (Expert Guide)? \url{http://goo.gl/vM360}
}-->

<!--\todoin{Hier im Ausblick erwähnen, dass Performance-Vergleiche anhand eigener Anwendungen folgen, bezogen auf SIMD stuff aber auch Go auf embedded (vs. C/C++)}-->

<!--\todoin{Literatur Recherche zu
Concurrency und aktuellen MPSOC.
(Multiprocessor System-on-Chip: Hardware Design and Tool Integration)
Paralleliserung von Laserscanner und Kamera Datenstromverarbeitung unter Linux mit GO im Wettstreit mit C-Threads-pur.
}-->

<!--\todoin{Mention that \citep{Herlihy2012} will be used as an additional "fundation work", since it was revised in 2012 and thus is newer, but was not available to the author until now.}-->

<!--\todoin{Ausblick: As a result, the impact of Go's garbage collector on application performance will have to be closely evaluated (also scheduling stuff)}-->

<!--\todoin{Mention that the NEON Alchemy Lab stuff must be confirmed.}-->

<!--\todoin{Schwarz: The Art of Multiprocessor Programming sollten wir auch auf dem Zettel haben, da hier eine neue Auflage vorliegt. \url{http://goo.gl/TZU73}
}-->

<!--\todoin{short essay outlook: In the course of my master seminar, these hypotheses will be evaluated by extracting performance critical elements from the mentioned FAUST application and implementing them in Go and C, using state of the art dataflow parallelization algorithms. These implementations will then be compared to their original (non-parallelized) counterparts, in terms of performance, ease of implementation and implementation complexity.}-->





<!--
% Bibliography
\clearpage
\begin{flushleft}
    \bibliography{bibliography}
\end{flushleft}
-->
