SKIR: Just-In-Time Compilation for Parallelism with LLVM
Total Page:16
File Type:pdf, Size:1020Kb
Computer Systems @ Colorado COMPUTER AND CYBERPHYSICAL SYSTEMS RESEARCH AT COLORADO http://systems.cs.colorado.edu SKIR:SKIR: Just-in-TimeJust-in-Time CompilationCompilation forfor ParallelismParallelism withwith LLVMLLVM JeffJeff FifieldFifield UniversityUniversity ofof ColoradoColorado 20112011 LLVMLLVM DevelopersDevelopers MeetingMeeting NovemberNovember 18,18, 20112011 The SKIR Project is: Stream and Kernel Intermediate Representation (SKIR) SKIR = LLVM + Streams/Kernels Stream language front-end compilers SKIR code optimizations for stream parallelism Dynamic scheduler for shared memory x86 LLVM JIT back-end for CPU LLVM to OpenCL back-end for GPU WhatWhat isis StreamStream Parallelism?Parallelism? stateful Regular data-centric computation kernel Independent processing stages data with explicit communication e n parallel i l e kernel p i Pipeline, Data, and Task p Parallelism split/scatter Examples: fine Digital Signal Processing Encoding/Decoding y t i r a Compression, Cryptography l u streams n join/gather a Video, Audio r g Network Processing Real-time “Big Data” services coarse task data FormalFormal ModelsModels ofof StreamStream ParallelismParallelism Kahn Process Networks (KPN) [1] computation as a graph of independent processes communication over unidirectional FIFO channels block on read to empty channel, never block on write deterministic kernels ⇒ deterministic network Synchronous Data Flow Networks (SDF) [2] restriction of KPN where kernel have fixed I/O rates allows better compiler analysis allows static scheduling techniques [1] G. Kahn, The semantics of a simple language for parallel programming, Information Processing (1974), 471–475. [2] E. A. Lee and D. G. Messerschmitt, Static scheduling of synchronous data flow programs for digital signal processing, IEEE Transactions on Computing 36 (1987), no. 1, 24–35. WhyWhy StreamStream Parallelism?Parallelism? ItIt cancan targettarget increasinglyincreasingly diversediverse parallelparallel hardwarehardware Intel Knights Corner: 50 Cores AMD Bulldozer: Shared FPU AMD Llano: On Die GPU Intel Sandy Bridge: Shared L3 between Gfx, CPU NVIDIA Tegra 3: Asymmetric Multicore WhyWhy StreamStream Parallelism?Parallelism? ItIt SupportsSupports aa varietyvariety ofof datadata centriccentric applicationsapplications Audio processing Physics Simulations Image processing Financial Applications Compression Network Processing Encryption Computational Bio Data Mining Game Physics Software Radio Twitter Parsing 2-D and 3-D Graphics Marmot Detection And many more... WhyWhy SKIR?SKIR? EmbeddedEmbedded domaindomain specificspecific parallelismparallelism isis usefuluseful Embed domain specific knowledge Application Source Code into the language, compiler, or runtime system Parallel Programming model tailored to your problem Algorithm higher level of abstraction ⇒ higher productivity restricted prog. model ⇒ higher performance Standard Your favorite language, with better parallelism Compiler Examples: Offline Runtime PLINQ – optimizable embedded query language Domain ArBB – vector computation on parallel arrays Specific Compiler CUDA – memory and execution models for GPUs SKIR – language independent stream parallelism Parallel Hardware SKIR:SKIR: OverviewOverview Application Organized as JIT Compiler Source Code SKIR for performance portability Algorithm for dynamic program graphs for dynamic optimization Standard Compiler SKIR intrinsics for LLVM Offline Runtime stream communication SKIR Compiler & static or dynamic stream Runtime graph manipulation Parallel & Heterogeneous Hardware CPU GPU FPGA ??? now future work bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { SKIRSKIR ExampleExample int data skir.pop(0, &data) data += *state skir.push(0, &data) return false } SKIR pseudo-code bool CONSUMER (int *state, void *ins[], void *outs[]) { int data Construct and execute a if (*state == 0) return true skir.pop(0, &data) print(data) 4 stage pipeline *state = *state - 1 return false } main() { int counter = 0 int limit = 20 int one = 1 int neg = -1 stream Q1[2], Q2[2], Q3[2] kernel K1, K2, K3, K4 Q1[0] = skir.stream(sizeof(int)); Q1[1] = 0 Q2[0] = skir.stream(sizeof(int)); Q2[1] = 0 Q3[0] = skir.stream(sizeof(int)); Q3[1] = 0 K1 = skir.kernel(PRODUCER, &counter) K2 = skir.kernel(ADDER, &one) K3 = skir.kernel(ADDER, &neg) K4 = skir.kernel(CONSUMER, &limit) skir.call(K1, NULL, Q1) skir.call(K2, Q1, Q2) skir.call(K3, Q2, Q3) skir.call(K4, Q3, NULL) skir.wait(K4) } } bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { int data skir.pop(0, &data) data += *state skir.push(0, &data) return false } bool CONSUMER (int *state, void *ins[], void *outs[]) { int data if (*state == 0) return true skir.pop(0, &data) print(data) *state = *state - 1 return false } main() { int counter = 0 int limit = 20 int one = 1 int neg = -1 stream Q1[2], Q2[2], Q3[2] kernel K1, K2, K3, K4 Q1[0] = skir.stream(sizeof(int)); Q1[1] = 0 Q2[0] = skir.stream(sizeof(int)); Q2[1] = 0 Q3[0] = skir.stream(sizeof(int)); Q3[1] = 0 K1 = skir.kernel(PRODUCER, &counter) K2 = skir.kernel(ADDER, &one) K3 = skir.kernel(ADDER, &neg) K4 = skir.kernel(CONSUMER, &limit) skir.call(K1, NULL, Q1) skir.call(K2, Q1, Q2) skir.call(K3, Q2, Q3) skir.call(K4, Q3, NULL) skir.wait(K4) } } bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { int data PRODUCER skir.pop(0, &data) data += *state skir.push(0, &data) return false } bool CONSUMER (int *state, void *ins[], void *outs[]) { int data if (*state == 0) return true skir.pop(0, &data) print(data) ADDER *state = *state - 1 return false } main() { int counter = 0 int limit = 20 int one = 1 int neg = -1 stream Q1[2], Q2[2], Q3[2] kernel K1, K2, K3, K4 ADDER Q1[0] = skir.stream(sizeof(int)); Q1[1] = 0 Q2[0] = skir.stream(sizeof(int)); Q2[1] = 0 Q3[0] = skir.stream(sizeof(int)); Q3[1] = 0 K1 = skir.kernel(PRODUCER, &counter) K2 = skir.kernel(ADDER, &one) K3 = skir.kernel(ADDER, &neg) K4 = skir.kernel(CONSUMER, &limit) skir.call(K1, NULL, Q1) skir.call(K2, Q1, Q2) CONSUMER skir.call(K3, Q2, Q3) skir.call(K4, Q3, NULL) skir.wait(K4) } } bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { int data PRODUCER skir.pop(0, &data) data += *state skir.push(0, &data) return false } bool CONSUMER (int *state, void *ins[], void *outs[]) { int data if (*state == 0) return true skir.pop(0, &data) print(data) ADDER *state = *state - 1 return false } main() { int counter = 0 int limit = 20 int one = 1 int neg = -1 stream Q1[2], Q2[2], Q3[2] kernel K1, K2, K3, K4 ADDER Q1[0] = skir.stream(sizeof(int)); Q1[1] = 0 Q2[0] = skir.stream(sizeof(int)); Q2[1] = 0 Q3[0] = skir.stream(sizeof(int)); Q3[1] = 0 K1 = skir.kernel(PRODUCER, &counter) K2 = skir.kernel(ADDER, &one) K3 = skir.kernel(ADDER, &neg) K4 = skir.kernel(CONSUMER, &limit) skir.call(K1, NULL, Q1) skir.call(K2, Q1, Q2) CONSUMER skir.call(K3, Q2, Q3) skir.call(K4, Q3, NULL) skir.wait(K4) } } bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { int data PRODUCER skir.pop(0, &data) data += *state skir.push(0, &data) return false } bool CONSUMER (int *state, void *ins[], void *outs[]) { int data if (*state == 0) return true skir.pop(0, &data) print(data) ADDER *state = *state - 1 return false } main() { int counter = 0 int limit = 20 int one = 1 int neg = -1 stream Q1[2], Q2[2], Q3[2] kernel K1, K2, K3, K4 ADDER Q1[0] = skir.stream(sizeof(int)); Q1[1] = 0 Q2[0] = skir.stream(sizeof(int)); Q2[1] = 0 Q3[0] = skir.stream(sizeof(int)); Q3[1] = 0 K1 = skir.kernel(PRODUCER, &counter) K2 = skir.kernel(ADDER, &one) K3 = skir.kernel(ADDER, &neg) K4 = skir.kernel(CONSUMER, &limit) skir.call(K1, NULL, Q1) skir.call(K2, Q1, Q2) CONSUMER skir.call(K3, Q2, Q3) skir.call(K4, Q3, NULL) skir.wait(K4) } } bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { int data PRODUCER skir.pop(0, &data) data += *state skir.push(0, &data) return false } bool CONSUMER (int *state, void *ins[], void *outs[]) { int data if (*state == 0) return true skir.pop(0, &data) print(data) ADDER *state = *state - 1 return false } main() { int counter = 0 int limit = 20 int one = 1 int neg = -1 stream Q1[2], Q2[2], Q3[2] kernel K1, K2, K3, K4 ADDER Q1[0] = skir.stream(sizeof(int)); Q1[1] = 0 Q2[0] = skir.stream(sizeof(int)); Q2[1] = 0 Q3[0] = skir.stream(sizeof(int)); Q3[1] = 0 K1 = skir.kernel(PRODUCER, &counter) K2 = skir.kernel(ADDER, &one) K3 = skir.kernel(ADDER, &neg) K4 = skir.kernel(CONSUMER, &limit) skir.call(K1, NULL, Q1) skir.call(K2, Q1, Q2) CONSUMER skir.call(K3, Q2, Q3) skir.call(K4, Q3, NULL) skir.wait(K4) } } bool PRODUCER (int *state, void *ins[], void *outs[]) { *state = *state + 1 skir.push(0, state) return false } bool ADDER (int *state, void *ins[], void *outs[]) { int data PRODUCER skir.pop(0, &data) data += *state skir.push(0, &data) return false } bool CONSUMER (int *state, void *ins[], void *outs[]) { int data if (*state == 0) return true skir.pop(0, &data) print(data)