Jump to content

Stream processing

From Wikipedia, the free encyclopedia
(Redirected from Stream programming)

In computer science, stream processing (also known as event stream processing, data stream processing, or distributed stream processing) is a programming paradigm that views streams, or sequences of events in time, as the central input and output objects of computation. Stream processing encompasses dataflow programming, reactive programming, and distributed data processing.[1] Stream processing systems use streaming algorithms to trace parallel processing for data streams. The software stack for these systems includes components such as programming models and query languages, for expressing computation; stream management systems for distribution and scheduling; and hardware components for acceleration, including floating-point units, graphics processing units, and field-programmable gate arrays.[2]

The stream processing paradigm simplifies parallel software and hardware by restricting the kinds of parallel computation that can be performed. Given a sequence of data (a stream), a series of operations (kernel functions) is applied to each element in the stream. Kernel functions are usually pipelined, and efficient local on-chip memory reuse is attempted in order to minimize bandwidth loss associated with external memory interaction. Uniform streaming, in which a single kernel function is applied to all elements in the stream, is typical. Because the kernel and stream abstractions expose data dependencies, compiler tools can fully automate and optimize on-chip management tasks. Stream processing hardware can use techniques such as scoreboarding to initiate direct memory access (DMA) when dependencies are resolved. The elimination of manual DMA management reduces software complexity, while the reduced reliance on hardware cached I/O decreases the memory footprint required by specialized computational units such as arithmetic logic units.

During the 1980s stream processing was explored within dataflow programming. One example is the language SISAL (Streams and Iteration in a Single Assignment Language).

Applications

[edit]

Stream processing can be viewed as a compromise, driven by a data-centric model that works well for traditional DSP- or GPU-type applications (such as image, video and digital signal processing), but less well for general purpose processing with more randomized data access (such as databases). By sacrificing some flexibility in the model, this approach can enable easier, faster, and more efficient execution. Depending on the context, processor design can be tuned for maximum efficiency or for a trade-off with flexibility.

Stream processing is especially suitable for applications that exhibit three characteristics:[citation needed]

  • Compute intensity, defined as the number of arithmetic operations per I/O or global memory reference. In many signal processing applications, this ratio is well over 50:1 and continues to increase with algorithmic complexity.
  • Data parallelism, which exists in a kernel when the same function is applied to all records of an input stream, allowing multiple records to be processed simultaneously without waiting for results from previous records.
  • Data locality, a form of temporal locality common in signal and media processing applications, in which data is produced once, read once or twice later in the application, and then not used again. Intermediate streams passed between kernels, as well as intermediate data within kernel functions, can capture this locality directly in the stream processing programming model.

Examples of records within streams include:

  • In graphics, each record may consist of vertex, normal, and color information for a triangle.
  • In image processing, each record may be a single pixel from an image.
  • In a video encoder, each record may be 256 pixels, forming a macroblock of data.
  • In wireless signal processing, each record could be a sequence of samples received from an antenna.

For each record, processing is typically limited to reading from the input, performing operations on the data, and writing the result to the output. Multiple inputs and outputs are possible, but memory is not both read and written within the same application.

Code examples

[edit]

By way of illustration, the following code fragments demonstrate the detection of patterns within event streams. The first example shows the processing of a data stream using a continuous SQL query: an ongoing query that processes incoming data based on timestamps and window duration. This code fragment illustrates a JOIN of two data streams: one representing stock orders and the other representing the resulting stock trades. The query outputs a stream of all orders matched by a trade within one second of the order being placed. The output stream is sorted by timestamp; in this case, the timestamp originates from the orders stream.

SELECT DataStream
   Orders.TimeStamp, Orders.orderId, Orders.ticker,
   Orders.amount, Trade.amount
FROM Orders
JOIN Trades OVER (RANGE INTERVAL '1' SECOND FOLLOWING)
ON Orders.orderId = Trades.orderId;

Another sample code fragment detects weddings within a stream of external events, such as church bells ringing, the appearance of a man in a tuxedo or morning suit, a woman in a white gown, and rice being thrown. A "complex" or "composite" event is the high-level event inferred from these constituent events: in this case, that a wedding is occurring.

WHEN Person.Gender EQUALS "man" AND Person.Clothes EQUALS "tuxedo"
FOLLOWED-BY
  Person.Clothes EQUALS "gown" AND
  (Church_Bell OR Rice_Flying)
WITHIN 2 hours
ACTION Wedding

Comparison to prior parallel paradigms

[edit]

Early computers were based on a sequential execution paradigm. Traditional CPUs utilize a single instruction, single data (SISD) architecture, meaning they conceptually perform one operation at a time. As the computing demands increased, the volume of data to be processed grew rapidly, exposing the limitations of sequential programming models. Various approaches were explored to enable large-scale computation, primarily by exploiting parallel execution.

One major outcome of these efforts was single instruction, multiple data (SIMD), an architecture that allows a single instruction to operate on multiple data elements simultaneously. In general-purpose microprocessors, SIMD is frequently implemented via SIMD within a register (SWAR). By incorporating distinct execution structures for separate infrastructure streams, multiple instruction, multiple data (MIMD) parallelism could also be achieved.

Although these paradigms are effective, physical hardware implementations face strict constraints, including memory alignment requirements, synchronization overhead, and limited scaling. Consequently, relatively few SIMD processors survived as stand-alone components; most have been integrated into general-purpose CPUs.

A fundamental example of these paradigms is a program that adds two arrays, each containing 100 four-component vectors (totaling 400 numerical values).

Conventional, sequential paradigm

[edit]

In the standard sequential paradigm, the operation is executed iteratively using a single loop:

for (int i = 0; i < 400; i++) {
    result[i] = source0[i] + source1[i];
}

While structural variations exist—such as the use of nested inner loops or array-of-structures data layouts—the underlying computation relies fundamentally on this linear execution model.

Parallel SIMD paradigm, packed registers (SWAR)

[edit]
// for each vector
for (int elem = 0; elem < 100; elem++) {
    vectorSum(result[elem], source0[elem], source1[elem]);
}

This model abstractly demonstrates the paradigm by assuming a generic vector_sum instruction. While this abstraction reflects how instruction intrinsics operate in practice, it omits underlying hardware implementation details—such as explicit data formats and component bit-widths—for clarity.

By operating on packed data structures, this method reduces the number of individual arithmetic instructions required to process the array components. Loop control and jump overhead are also reduced due to the lower iteration count. These efficiency gains are the direct result of executing multiple arithmetic operations simultaneously within a single instruction execution step.

However, because a packed SIMD register has a fixed bit-width capacity, scalability is inherently bounded by the maximum register size. In this scenario, hardware acceleration is capped by the vector width of four parallel operations, a configuration standard in architectures such as AltiVec and Streaming SIMD Extensions (SSE).

Parallel stream paradigm (SIMD/MIMD)

[edit]
// This is a fictional language for demonstration purposes.
elements = array streamElement([number, number])[100]
kernel = instance streamKernel("@arg0[@iter]")
result = kernel.invoke(elements)

In the stream processing paradigm, data is treated as an unbounded, continuous sequence of elements rather than a static dataset. Instead of managing iteration explicitly, the program defines a dataflow sequence, allowing the execution environment to apply the compute kernel function automatically as new data elements arrive. Although a 1:1 mapping between input and output data is commonly used for simplicity, it is not an inherent architectural requirement; kernels can perform complex transformations, aggregation windows, or stateful modifications.

Compilers optimized for this paradigm can perform extensive automated code transformations, such as loop unrolling. This abstraction allows throughput to scale transparently with hardware capacity, enabling the utilization of hundreds of arithmetic logic units (ALUs).[3][4] Minimizing complex, unpredictable data access patterns ensures that a higher percentage of the hardware's peak execution capacity is accessible.

While stream processing shares characteristics with broader SIMD and MIMD architectures, the concepts remain distinct. Although SIMD hardware often executes operations in a pipelined or streaming manner, standard SIMD performance characteristics differ; the stream processing model enforces structured dataflow and explicit memory management that permits significantly higher execution efficiency.

When implemented on general-purpose architectures such as standard CPUs, stream processing abstractions frequently yielded limited performance gains, with some historical studies noting an execution speedup of only approximately 1.5x.[5] In contrast, early dedicated stream processors achieved performance increases exceeding 10x, primarily due to specialized hardware-managed register files and higher levels of parallel execution units.[6]

Despite variations in flexibility across implementation models, stream processing hardware generally imposes strict constraints on both kernel complexity and stream dimension sizes. For example, consumer-grade graphics processing hardware historically lacked high-precision arithmetic support, lacked complex pointer indirection capabilities, and enforced strict limits on maximum instruction counts.

Research

[edit]

Early research in stream processing emerged in the late 1990s and early 2000s, driven by efforts to scale arithmetic intensity for graphics pipelines and high-performance computing. Academic projects, notably at Stanford University, pioneered early stream processing architectures and compiler designs that proved foundational to modern data-parallel hardware.[7] Concurrently, industrial researchers, including groups at AT&T, explored stream-enhanced processors to optimize signal processing and telecommunications workloads as graphics processing units rapidly gained performance and programmability.[8]

Following these foundational efforts, the paradigm transitioned from specialized experimental hardware to mainstream software ecosystems, resulting in the development of numerous dedicated stream processing languages and frameworks.

Programming and data layout considerations

[edit]

A primary challenge in parallel computing is the complexity of mapping algorithms to hardware architectures while maintaining software development velocity and runtime performance. Early stream hardware, such as the Stanford Imagine prototype, mitigated this by utilizing a single-threaded programming model that abstracted memory allocation, data dependencies, and direct memory access (DMA) scheduling. This task division emerged from research at the Massachusetts Institute of Technology (MIT) and Stanford University, which demonstrated that human programmers are highly effective at high-level algorithmic partitioning, whereas automated compilation tools excel at optimizing complex memory allocation and scheduling routines. In contrast, asymmetric multicore architectures, such as the Cell Broadband Engine, shift structural partitioning, process synchronization, and load balancing overhead directly onto the software developer.

Data structure layout significantly impacts the execution efficiency of parallel paradigms, typically requiring a choice between an array-of-structures (AoS) and a structure-of-arrays (SoA). In general-purpose software engineering, developers conventionally represent data entities in memory—for example, the location of a particle in 3D space, the color of a ball, and its size—as below below:

 // A particle in a three-dimensional space.
struct Particle {
    double x; 
    double y;
    double z;

    // 8 bit per channel, say we care about RGB only
    unsigned byte color[3];
    float size;
    // ... and many other attributes may follow...
};

When multiple entities exist in sequence, they are allocated end-to-end, forming an array of structures (AoS) topology. If an algorithm processes only a single attribute across all elements—such as modifying only the 3D coordinates—the execution engine must skip over the unused attributes in memory. Because conventional cache lines fetch memory in contiguous blocks, loading unneeded attributes results in inefficient cache utilization and wasted memory bandwidth. Furthermore, standard SIMD operations require input elements to be contiguous and properly aligned in memory to fill vector lanes efficiently.

To optimize data streaming and vector execution, attributes can be separated into distinct parallel blocks using a structure of arrays (SoA) layout. An SoA representation isolates identical fields into individual, contiguous arrays, as shown below:

struct Particle {
    double* x; 
    double* y;
    double* z;

    unsigned byte* colorRed; 
    unsigned byte* colorBlue;
    unsigned byte* colorGreen;

    float* size;
};

While the structure of arrays (SoA) layout optimizes uniform data paths, it introduces distinct architectural trade-offs. If a routine must simultaneously operate on multiple disparate attributes of a single entity, those attributes may reside far apart in virtual memory, resulting in severe cache misses and increased address translation overhead. Furthermore, ensuring that each separate array meets hardware memory alignment boundaries can necessitate data padding, which increases overall memory footprints. Dynamic memory management also becomes highly complex when elements must be added or removed, as modifications require shifting elements synchronously across multiple disconnected arrays.

Conversely, dedicated stream processing architectures heavily utilize structured abstractions to unify these layouts. In contemporary graphics processing units (GPU) vertex pipelines, hardware provides a fixed number of attribute slots—historically standardizing around 16 input lines. The application specifies the component count and data type format for each input stream, though hardware support is typically restricted to primitive numeric data types. These independent attributes are bound to a cohesive memory block via an explicit stride parameter. By adjusting the byte stride between consecutive array elements, developers can format the data stream as either interleaved arrays of structures (AoS) or separate structures of arrays (SoA). At the execution stage, the GPU hardware automatically gathers these disparate attributes into a unified parameter packet—such as an explicit kernel structure or built-in global registers—executes the operations in parallel, and scatters the output results to an output buffer for subsequent processing pipeline stages.

Modern stream processing frameworks introduce first-in, first-out (FIFO) abstractions to represent data execution pipelines as decoupled, directional topologies. This design allows developers to define explicit data-parallel dependencies while enabling the runtime environment to coordinate memory allocation, threading boundaries, and cross-kernel task scheduling transparently.

An implementation of this streaming model in C++ is RaftLib, an open-source template library that enables developers to chain independent computational kernels into a dataflow graph using overloaded C++ stream operators. To demonstrate this paradigm, the following code initializes an asynchronous text-generation stream linked to a standard output kernel:

import <raft>;
import <raftio>;

import std;

using String = std::string;

using RaftKernel = raft::kernel;
using RaftKernelStatus = raft::kstatus;
using RaftMap = raft::map;
using RaftPrint = raft::print;

class HelloWorld : public RaftKernel {
public:
    HelloWorld() {
        output.addPort<String>("0"); 
    }

    virtual RaftKernelStatus run() {
        output["0"].push("Hello World\n");
        return raft::stop; 
    }
};

int main(int argc, char* argv[]) {
    // instantiate print kernel
    RaftPrint<String> p;

    // instantiate hello world kernel
    HelloWorld hello;

    // make a map object
    RaftMap m;

    // add kernels to map, both hello and p are executed concurrently
    m += hello >> p;

    // execute the map
    m.exe();

    return 0;
}

Models of computation

[edit]

Beyond high-level procedural programming languages, stream processing applications are formally structured via distinct models of computation (MoCs). These include structured dataflow models and process-based concurrent frameworks (such as Kahn process networks), which mathematically represent computational dependencies and pipelined execution stages.

Processor architecture comparisons

[edit]

Historically, general-purpose central processing units (CPUs) implemented increasingly complex, multi-tiered memory access hierarchies to mitigate the growing performance discrepancy between raw core execution speeds and external memory bandwidth. To mask these memory latencies, a substantial percentage of conventional CPU die area is allocated to automated cache tracking, branch prediction, and speculative execution logic. Consequently, only a minor fraction of the hardware footprint—historically estimated at less than 10%—is dedicated directly to arithmetic logic units (ALUs).

Stream processing hardware structures minimize this management overhead by leveraging explicit dataflow restrictions. Structurally, stream processors typically operate within a co-processing environment; a primary host CPU remains responsible for executing the operating system, orchestrating system resource allocations, and managing application-level thread boundaries, while the stream processor focuses entirely on high-throughput arithmetic acceleration.

To maintain maximum throughput, stream processors utilize wide, dedicated memory buses. Early designs utilized multi-segment crossbars across varying bus widths (such as 128-bit or 256-bit topologies), prioritizing memory bandwidth over latency. This contrasts with historical scalar computing platforms, which traditionally relied on narrower single-channel memory channels. Furthermore, memory access paths within a stream engine remain highly predictable; stream data dimension bounds are fixed explicitly upon kernel invocation, transforming arbitrary multiple-pointer indirections into bounded indirection chains that resolve to explicit stream memory regions.

Because execution units are organized into dense parallel arithmetic clusters managed by convergent VLIW and SIMD control paradigms, read and write operations are processed via bulk streaming transfers. These systems decouple intermediate application data, completing a vast majority of calculation tasks directly on-chip through an explicit three-tiered data bandwidth hierarchy.

Hardware-in-the-loop issues

[edit]

Although an order of magnitude speedup can be achieved by parallel streaming architectures, not all workloads benefit from this model. Inter-processor communication latency represents a significant bottleneck. While modern system buses, such as PCI Express, provide high-bandwidth, full-duplex communication pipelines, the latency overhead of transferring data between the host memory and the stream processor's discrete memory space remains substantial. Consequently, utilizing a stream coprocessor for small datasets is frequently inefficient. Because reconfiguring execution state or compiling a new kernel introduces significant latency, the architecture also incurs severe performance penalties when processing small stream dimensions—a phenomenon known as the short stream effect.

Early programmable graphics hardware heavily relied on deep, specialized execution pipelines to maximize throughput. However, frequent state changes—such as switching shader programs or updating memory bindings—disrupted pipeline efficiency and introduced heavy driver validation overhead. To mitigate these penalties in real-time rendering pipelines, developers introduced software-level optimization patterns such as "über-shaders" (large, monolithic kernels that handle multiple material types via conditional branches) and "texture atlases" (consolidating independent texture resources into a single, contiguous memory layout to avoid binding switches). While these techniques originated within real-world video game engines, the underlying principles apply broadly to generic stream processing to maximize kernel execution runtime and prevent hardware stall states.

Historical examples and evolution

[edit]

Early dedicated architecture

[edit]
  • Commodore Amiga Blitter (1985): An early hardware implementation of stream-like data manipulation. The Amiga blitter operated as a dedicated graphics coprocessor, capable of combining up to three distinct memory bitstream sources through bitwise logic operations to generate a single output stream. This pipeline moved data at a peak input bandwidth of 42 megabits per second, bypassing the main CPU to accelerate 2D graphics rendering.
  • Stanford Imagine Project (1996–2002): Funded by DARPA, Intel, and Texas Instruments, this academic research project pioneered the modern stream architecture. The project developed a flexible prototype chip that combined an array of arithmetic logic units (ALUs) with a centralized, software-managed Stream Register File (SRF) to maximize energy efficiency and computational throughput.
  • Stanford Merrimac (2004): A follow-up academic initiative that extended the Imagine streaming architecture into the supercomputing domain. It utilized specialized interconnection networks to deliver high performance-per-dollar ratios for scientific computing workloads compared to standard cluster computers of its era.[9]
  • Stream Processors, Inc. Storm-1 (2007): A commercial spin-off of the Stanford Imagine project. The Storm-1 architecture targeted high-end digital signal processing (DSP) markets, such as video conferencing and digital surveillance. It scaled performance from 30 to 220 billion operations per second (GOPS) by clustering streaming ALUs on a single chip.

Graphics processing units (GPUs)

[edit]

Modern graphics processing units (GPUs) represent the most widespread commercial evolution of stream processing. Their architectural progression transitioned hardware from rigid, fixed-function pipelines into general-purpose stream engines:

  • Fixed-Function Era (Pre-2001): Early graphics hardware offered no explicit developer access to stream processing. Streaming operations were entirely abstracted inside graphics application programming interfaces (APIs), restricting hardware customization.
  • Early Vertex Programmability (2001–2002): Microarchitectures like the ATI R200 and Nvidia NV20 introduced explicit programmer control, but strictly for vertex processing pipelines. Fragment/pixel processing remained bound to older paradigms. A total lack of conditional execution (branching support) limited these early chips to simple, linear mathematical models, such as basic fluid dynamics simulations.
  • Dynamic Control Flow (2003–2004): Architectures like the ATI R300 and Nvidia NV40 introduced flexible control flow and branching. While constrained by instruction count ceilings and limited loop nesting depths, these chips allowed data streams to diverge based on runtime calculation states.
  • Unified Stream Architectures (2006–Present): Later generations unified independent vertex and pixel pipelines into homogenous arrays of programmable stream cores. Hardware iterations introduced specialized stream mechanics—such as hardware-level atomic operations and dynamic append/consume buffers—enabling complex, general-purpose GPU computing (GPGPU). This evolution spawned dedicated data-center product lines for High-Performance Computing (HPC), including the Nvidia Tesla and AMD FireStream brands.

Cell broadband engine

[edit]

Developed by an alliance of Sony, Toshiba, and IBM (STI), the Cell processor functions as a hybrid streaming architecture when paired with specialized software toolchains. The chip features a primary controlling processor—the Power Processing Element (PPE)—and an array of vector coprocessors called Synergistic Processing Elements (SPEs). Because each SPE possesses an independent instruction memory space and program counter, the chip behaves as a multiple instruction, multiple data (MIMD) environment. However, due to severe local memory constraints, software must utilize explicit direct memory access (DMA) commands to stream data sequentially through the SPEs. When software algorithms are completely restructured to adhere strictly to this stream programming model, the hardware's execution efficiency matches that of dedicated stream processors.

Stream programming libraries and languages

[edit]

Most stream processing frameworks build upon established general-purpose languages such as C, C++, or Java. These ecosystems extend baseline language capabilities through dedicated application programming interfaces (APIs), custom compiler pragmas, or custom domain-specific languages (DSLs) to define bounded kernel execution blocks and streaming topologies. Additionally, high-level programmable shading languages function fundamentally as hardware-targeted stream processing languages.[10]

Academic and open-source environments

[edit]
  • Auto-Pipe: Developed by the Stream Based Supercomputing (SBS) Lab at Washington University in St. Louis. It provides a heterogeneous development environment coordinating pipelines across CPUs (via C/C++ or Java), FPGAs (via Verilog/VHDL), and GPUs (via CUDA).
  • BeepBeep: A lightweight, Java-based event stream processing library engineered by the Formal Computer Science Lab at Université du Québec à Chicoutimi.
  • Brook: A foundational data-parallel streaming language extension developed by Stanford University to abstract GPU programming.
  • CAL Actor Language: A high-level dataflow programming language designed to author stateful operators (actors) that transform incoming token streams into deterministic output streams.
  • Cal2Many: A compiler framework from Halmstad University that translates high-level CAL actor code into target-specific low-level source, including parallel C and hardware description models.
  • HSTREAM: A directive-based source-to-source compiler extension designed to facilitate stream processing across heterogeneous CPU and GPU execution resources similar to OpenMP compilation patterns.
  • RaftLib: An open-source C++ template library that utilizes native C++ stream operators (>>) to assemble concurrent computational kernels into unified dataflow topologies.
  • SPar: A C++ domain-specific language designed by the Application Modelling Group (GMAP) at the Pontifical Catholic University of Rio Grande do Sul to express stream parallelism via attribute annotations.
  • StreamIt: A specialized language and compiler infrastructure developed at MIT designed specifically to optimize stream-parallel pipelines.

Commercial and proprietary architectures

[edit]
  • Jacket (AccelerEyes): A commercial engine designed to compile and accelerate MATLAB computing structures directly over GPU stream units.
  • Embiot (Telchemy): A lightweight embedded streaming analytics agent designed to process sensor streams directly within resource-constrained edge computing environments.
  • Floodgate: A stream-processing engine bundled historically with the Gamebryo engine to manage multi-core task distributions on consumer video game consoles.
  • PeakStream: A commercial spin-out of the Stanford Brook project, subsequently acquired by Google in 2007.
  • RapidMind: A commercial software platform that evolved from the University of Waterloo's Sh library, subsequently acquired by Intel in 2009.
  • SPADE (IBM): The Stream Processing Application Declarative Engine, which serves as the foundational declarative streaming language for IBM's enterprise System S platform.

Distributed and real-time event stream ecosystems

[edit]

Modern cluster-scale data infrastructure categorizes stream architectures based on their execution models:

  • Native Continuous Stream Processing: Frameworks that process elements individually as they arrive, optimizing for sub-second, real-time latencies:
    • Apache Storm: A native, low-latency distributed real-time computation system.
    • Apache Flink: A true streaming execution engine that treats batch computing strictly as a specialized subset of continuous streaming.
    • Apache Samza: A stateful distributed stream processing engine built over Apache Kafka.
  • Micro-Batch Stream Processing: Frameworks that achieve high-throughput stream semantics by automatically collecting incoming elements into tightly constrained time windows or miniature batches:
    • Apache Spark Streaming: An extension of the core Apache Spark API that ingests live data streams and processes them in small discrete chunks (micro-batches).
  • Distributed Logging and Messaging Fabrics: Infrastructure that serves as the persistent, append-only transport layer for continuous stream architectures:
    • Apache Kafka: A highly scalable, distributed event streaming platform used to ingest and store fault-tolerant streams of records.

Managed cloud streaming services

[edit]

See also

[edit]

References

[edit]
  1. A Short Intro to Stream Processing
  2. FCUDA: Enabling Efficient Compilation of CUDA Kernels onto FPGAs
  3. IEEE Journal of Solid-State Circuits:"A Programmable 512 GOPS Stream Processor for Signal, Image, and Video Processing", Stanford University and Stream Processors, Inc.
  4. Khailany, Dally, Rixner, Kapasi, Owens and Towles: "Exploring VLSI Scalability of Stream Processors", Stanford and Rice University.
  5. Gummaraju and Rosenblum, "Stream processing in General-Purpose Processors", Stanford University.
  6. Kapasi, Dally, Rixner, Khailany, Owens, Ahn and Mattson, "Programmable Stream Processors", Universities of Stanford, Rice, California (Davis) and Reservoir Labs.
  7. Eric Chan. "Stanford Real-Time Programmable Shading Project". Research group web site. Retrieved March 9, 2017.
  8. "Merrimac - Stanford Streaming Supercomputer Project". Group web site. Archived from the original on December 18, 2013. Retrieved March 9, 2017.
  9. Merrimac
  10. Memeti, Suejb; Pllana, Sabri (October 2018). "HSTREAM: A directive-based language extension for heterogeneous stream computing". 2018 IEEE International Conference on Computational Science and Engineering (CSE): 138–145. doi:10.1109/CSE.2018.00026.