Leveraging Aparapi to Help Improve Financial Java Application Performance



Similar documents
Introducing PgOpenCL A New PostgreSQL Procedural Language Unlocking the Power of the GPU! By Tim Child

Ensure that the AMD APP SDK Samples package has been installed before proceeding.

GPU ACCELERATED DATABASES Database Driven OpenCL Programming. Tim Child 3DMashUp CEO

Binomial option pricing model. Victor Podlozhnyuk

HETEROGENEOUS SYSTEM COHERENCE FOR INTEGRATED CPU-GPU SYSTEMS

AMD GPU Architecture. OpenCL Tutorial, PPAM Dominik Behr September 13th, 2009

Radeon GPU Architecture and the Radeon 4800 series. Michael Doggett Graphics Architecture Group June 27, 2008

NVIDIA CUDA GETTING STARTED GUIDE FOR MICROSOFT WINDOWS

#820 Computer Programming 1A

Writing Applications for the GPU Using the RapidMind Development Platform

AMD APP SDK v2.8 FAQ. 1 General Questions

Performance Evaluation of VMXNET3 Virtual Network Device VMware vsphere 4 build

Intel Media Server Studio - Metrics Monitor (v1.1.0) Reference Manual

ATI Radeon 4800 series Graphics. Michael Doggett Graphics Architecture Group Graphics Product Group

GPU System Architecture. Alan Gray EPCC The University of Edinburgh

PART IV Performance oriented design, Performance testing, Performance tuning & Performance solutions. Outline. Performance oriented design

NVIDIA CUDA GETTING STARTED GUIDE FOR MAC OS X

DELL. Virtual Desktop Infrastructure Study END-TO-END COMPUTING. Dell Enterprise Solutions Engineering

Power Benefits Using Intel Quick Sync Video H.264 Codec With Sorenson Squeeze

Full and Para Virtualization

JBoss Data Grid Performance Study Comparing Java HotSpot to Azul Zing

Java GPU Computing. Maarten Steur & Arjan Lamers

Course materials. In addition to these slides, C++ API header files, a set of exercises, and solutions, the following are useful:

New Technology Introduction: Android Studio with PushBot

Linux VM Infrastructure for memory power management

Can You Trust Your JVM Diagnostic Tools?

NVIDIA CUDA GETTING STARTED GUIDE FOR MAC OS X

INTEL PARALLEL STUDIO XE EVALUATION GUIDE

Accelerating Server Storage Performance on Lenovo ThinkServer

Maximum performance, minimal risk for data warehousing

Introduction to Java

NVIDIA GeForce GTX 580 GPU Datasheet

Computer Programming I & II*

Amazon EC2 XenApp Scalability Analysis

Gary Frost AMD Java Labs

Intel Retail Client Manager Audience Analytics

Effective Java Programming. efficient software development

Applications to Computational Financial and GPU Computing. May 16th. Dr. Daniel Egloff

HOW MANY USERS CAN I GET ON A SERVER? This is a typical conversation we have with customers considering NVIDIA GRID vgpu:

Using VMware VMotion with Oracle Database and EMC CLARiiON Storage Systems

can you effectively plan for the migration and management of systems and applications on Vblock Platforms?

x64 Servers: Do you want 64 or 32 bit apps with that server?

FLOATING-POINT ARITHMETIC IN AMD PROCESSORS MICHAEL SCHULTE AMD RESEARCH JUNE 2015

Implementation of Stereo Matching Using High Level Compiler for Parallel Computing Acceleration

NVIDIA GeForce Experience

NVIDIA GRID DASSAULT CATIA V5/V6 SCALABILITY GUIDE. NVIDIA Performance Engineering Labs PerfEngDoc-SG-DSC01v1 March 2016

JVM Performance Study Comparing Oracle HotSpot and Azul Zing Using Apache Cassandra

Introduction to OpenCL Programming. Training Guide

INTEL PARALLEL STUDIO EVALUATION GUIDE. Intel Cilk Plus: A Simple Path to Parallelism

Java Garbage Collection Characteristics and Tuning Guidelines for Apache Hadoop TeraSort Workload

Graphics Cards and Graphics Processing Units. Ben Johnstone Russ Martin November 15, 2011

Topics. Introduction. Java History CS 146. Introduction to Programming and Algorithms Module 1. Module Objectives

HP ProLiant BL660c Gen9 and Microsoft SQL Server 2014 technical brief

PARALLEL JAVASCRIPT. Norm Rubin (NVIDIA) Jin Wang (Georgia School of Technology)

FileMaker 11. ODBC and JDBC Guide

GeoImaging Accelerator Pansharp Test Results

AMD CodeXL 1.7 GA Release Notes

HP AppPulse Mobile. Adding HP AppPulse Mobile to Your Android App

Using Synology SSD Technology to Enhance System Performance Synology Inc.

High-Performance Business Analytics: SAS and IBM Netezza Data Warehouse Appliances

Xeon+FPGA Platform for the Data Center

An Oracle Benchmarking Study February Oracle Insurance Insbridge Enterprise Rating: Performance Assessment

The Shortcut Guide to Balancing Storage Costs and Performance with Hybrid Storage

An Oracle Technical White Paper November Oracle Solaris 11 Network Virtualization and Network Resource Management

Achieving Nanosecond Latency Between Applications with IPC Shared Memory Messaging

Packet-based Network Traffic Monitoring and Analysis with GPUs

Using VMWare VAAI for storage integration with Infortrend EonStor DS G7i

SierraVMI Sizing Guide

High Performance Computing in CST STUDIO SUITE

System Requirements Table of contents

Accelerating Enterprise Applications and Reducing TCO with SanDisk ZetaScale Software

An Oracle White Paper August Oracle VM 3: Server Pool Deployment Planning Considerations for Scalability and Availability

Stream Processing on GPUs Using Distributed Multimedia Middleware

The MAX5 Advantage: Clients Benefit running Microsoft SQL Server Data Warehouse (Workloads) on IBM BladeCenter HX5 with IBM MAX5.

Introduction to GPU Computing

L20: GPU Architecture and Models

Application Note: AN00141 xcore-xa - Application Development

Practical Performance Understanding the Performance of Your Application

Le langage OCaml et la programmation des GPU

qwertyuiopasdfghjklzxcvbnmqwerty uiopasdfghjklzxcvbnmqwertyuiopasd fghjklzxcvbnmqwertyuiopasdfghjklzx cvbnmqwertyuiopasdfghjklzxcvbnmq

Lecture 1 Introduction to Android

Virtuozzo Virtualization SDK

Mobile App Design Project #1 Java Boot Camp: Design Model for Chutes and Ladders Board Game

Performance Improvement In Java Application

Virtualizing a Virtual Machine

solution brief September 2011 Can You Effectively Plan For The Migration And Management of Systems And Applications on Vblock Platforms?

VMware Virtual SAN Backup Using VMware vsphere Data Protection Advanced SEPTEMBER 2014

OpenACC 2.0 and the PGI Accelerator Compilers

You re not alone if you re feeling pressure

FileMaker 12. ODBC and JDBC Guide

ANALYSIS OF RSA ALGORITHM USING GPU PROGRAMMING

Intel Media SDK Library Distribution and Dispatching Process

Bringing Big Data Modelling into the Hands of Domain Experts

Evaluation of Enterprise Data Protection using SEP Software

Transcription:

Leveraging Aparapi to Help Improve Financial Java Application Performance Shrinivas Joshi, Software Performance Engineer Abstract Graphics Processing Unit (GPU) and Accelerated Processing Unit (APU) offload of generic Java applications has been made possible in the past by mechanisms such as Java bindings for OpenCL /CUDA. Recently, frameworks such as Aparapi have made it extremely easy for Java applications to leverage hardware resources of OpenCL capable devices. By refactoring JQuantlib API to use Aparapi kernels, a process that took no more than two man weeks, for sufficiently large problem sizes we were able to achieve up to 20x improvements in performance of a sample Java based quantitative finance application. This article discusses how to implement and tune Aparapi kernels for data-parallel operations within Java applications and the performance gains that can be achieved using Aparapi framework. Introduction AMD recently announced the open source release of the Aparapi project. Aparapi aims at improving Java application performance by offloading data parallel operations to an OpenCL capable device such as a GPU. Aparapi supports GPUs from most major vendors. If you are not familiar with the Aparapi project we highly recommend you to read the following blog post by Gary Frost from AMD. http://blogs.amd.com/developer/2011/09/14/i-dont-always-write-gpu-code-in-java-but-when-i-do-ilike-to-use-aparapi/. While we were working on Aparapi it was evident that Aparapi would help us gain orders-of-magnitude performance improvements on embarrassingly data-parallel operations with relatively smaller data footprint. This applied to classic GPU-friendly workloads such as Nbody and Mandelbrot amongst others. We were interested in exploring how Aparapi could help a generic data parallel application which may not necessarily be scientific in nature. In this article we will talk about how we used the Aparapi project to achieve substantial performance gains on a sample financial Java application which uses a quantitative finance library called JQuantlib. We will discuss details of the approach we took to - identify data parallel opportunities, implement Aparapi kernels to offload those data parallel operations to an OpenCL capable device, and optimize the kernels. We will also talk about the performance improvements that were achieved using this approach.

Workload This section contains details of the workload that we used for the purpose of this study. Many of you, especially those with experience in quantitative finance domain, may know that JQuantlib is a Java based API that is used to derive valuation of financial instruments such as options, shares, bonds, futures etc. It is derived from C++ based Quantlib API. For more details on JQuantlib API please refer to the documentation at http://www.jquantlib.org/index.php/main_page We knew that some of the algorithms used for option pricing were data parallel in nature. So our first choice was to start with examples leveraging option pricing related API classes of JQuantlib. Fortunately, we found a sample Java program on the JQuantlib website which exercised different option pricing methods. See http://www.jquantlib.org/maven2/sites/jquantlib/xref/org/jquantlib/examples/equityoptions.html for the source code of this sample program. We modified this program so that it exercised only Binomial method based option pricing algorithms, also only for American options type. We also added code to capture execution time of these different option pricing algorithms. Appendix A has complete source code of our modified sample program. Methodology for Implementing Aparapi Kernels In this section we will go through the steps one could take to - identify opportunities for using Aparapi kernels in their Java workloads, implement Aparapi kernels and finally tune their implementation for better performance. Characteristics of code that can benefit from GPU offload Before we go in to the details of how to identify opportunities for using Aparapi kernels, let us understand the characteristics of operations that can benefit from GPU offload. First of all, the operations that you are trying to speed up need to be data parallel so that they can leverage large number of processing cores available on a GPU. Secondly, there is a trade-off between the cost incurred by data transfer between the CPU and GPU and the gains achieved by parallelizing operations on the GPU. Thus your application needs to spend enough time in computational operations to mitigate the cost of data transfer. These data parallel operations also need to be execution hot spots of your application for it to see sizeable gains from GPU offload. This was indeed the case with our JQuantlib based application. Identifying GPU friendly operations within your workload If you already know that there are parallelizable operations within your workload then that is a good place to start. You can also use profiling tools to identify execution hot spots and then look through the corresponding source code to see if they are or could be parallelized. In our case, we used AMD CodeAnalyst profiler and noticed that our sample application was stressing JQuantlib API methods which were indeed parallelizable. Generally, you would notice that tight loops performing some operations over primitive data types or primitive arrays could be parallelized. Certain seemingly sequential

operations could be parallelized with appropriate changes in the implementation logic; certain operations seem sequential in nature but their associative nature allows for parallelization. Implementing Aparapi kernels for data parallel operations Once you identify code sites that are better suited to be executed on GPU, you can start looking at ways to replace them with Aparapi kernels. There are certain restrictions on the kind of code that can be used in Aparapi kernels. For example, you can only use primitive data types, you can only use single dimensional arrays and you can generally only make calls to other methods declared in the same class as the initial run() method, amongst others. Please see http://code.google.com/p/aparapi/wiki/javakernelguidelines for more details on what kind of operations you can and cannot perform in Aparapi kernels. JQuantlib Aparapi Kernels In case of JQuantlib, we started with looking at one data-parallel code site at a time. Typically these code sites were 1 or 2 lines of code within a method of a particular class. We started defining corresponding Aparapi kernel class within the classes containing the parallelizable code. We defined setter methods within these kernel classes for passing in input data to the kernel. We instantiated an object of this kernel class within the static initializer of the containing class so that we could just make a call to kernel execute() method when control reached the original data parallel code site. Now we could replace the original code with code that generally would do 3 operations in the following sequence: Extract primitive input data from Java objects and call kernel setter methods to pass this data to the kernels. Execute the kernel. Extract output generated by kernels and assign it to appropriate variables in the original code. Following is a general structure of some of the JQuantlib classes before and after we introduced Aparapi kernels: Original code public class JQClass{ private final double instancevar1; private final double instancevar2; public JQClass () { // initialize instancevar1 and instancevar2 public void jqmethod(final int i, final JQObject inputbuffer, final JQObject outputbuffer) { // data parallel loop // here JQObject is a container type of class which // holds a double array amongst other variables for (int j = 0; j < i; j++) { // write to appropriate elements of double array in outputbuffer // using results derived from appropriate elements of double array

// from inputbuffer and instance variables. Code after integrating Aparapi kernel (see bold-faced text) import com.amd.aparapi.kernel; public class JQClass{ private final double instancevar1; private final double instancevar2; public JQClass () { // initialize instancevar1 and instancevar2 private static class JQClassKernel extends Kernel{ private double[] kernelinputbuffer; private double[] kerneloutputbuffer; private double kernelinstancevar1; private double kernelinstancevar2; /** setter for kernelinputbuffer */ /** setter for kerneloutputbuffer */ /** setter for kernelinstancevar1 */ /** setter for kernelinstancevar2 */ @Override public void run() { int gid = getglobalid(); // set kerneloutputbuffer[gid] to appropriate value derived // using kernelinputbuffer, kernelinstancevar1 and // kernelinstancevar2 static JQClassKernel jqckernel = new JQClassKernel(); public void jqmethod(final int i, final JQObject inputbuffer, final JQObject outputbuffer) { double[] _inputbuffer = //extract double array from inputbuffer object; double[] _outputbuffer = new double[size_of_outputbuffer_array]; jqckernel.setinputbuffer(_inputbuffer); jqckernel.setoutputbuffer(_outputbuffer); jqckernel.setinstancevar1(instancevar1); jqckernel.setinstancevar2(instancevar2); jqckernel.execute(i); // assign kernel modified values of _outputbuffer to the primitive // double array of outputbuffer object

Tuning Aparapi kernels A problem encountered with the above mentioned approach was that we did not see substantial gains from GPU offload. This section talks about some of the ways to tune Aparapi kernel implementation help achieve substantial gains from GPU offload. Increase compute and reduce data transfer The reason we didn t see gains from GPU offload was that we were incurring a lot of data transfer cost and were doing far too little compute operations on the GPU. We were also incurring this cost at multiple code sites. It became clear that the way to address this issue was to find a mechanism to increase compute operations per kernel and to also reduce the associated data transfer cost. Merging logic from multiple kernels in to fewer numbers of kernels was the way to achieve this effect. To get the implementation of these merged kernels right we had to define new getter methods in some of the JQuantlib API classes. These getter methods allowed us to query certain data from the classes containing the merged kernel, which was not possible before. In our case it was fairly easy to merge kernels because the control flow was such that one data parallel code site was followed by call to another data parallel code site with little or no side effect (between the calls) on the data variables involved. Merged kernels allowed us to increase compute operations within the kernel and to also reduce the data transfer cost, both of which were bottlenecks previously. However, note that in this process of merging multiple kernels we had to lose some object oriented qualities of the JQuantlib API. In that, we defined getter methods where they were not required previously and we consolidated certain operations performed by multiple Java classes in fewer number of classes. Explicit buffer management We also came across scenarios where Aparapi s explicit buffer management feature came in handy. This feature allows more fine-grained user control of which data gets transferred between host and OpenCL device (and vice versa) and when the data transfer happens. At certain code sites we were not reading output buffers on the host side between successive kernel executions. Also, we were only reading either of the output buffers depending on some runtime condition. Using explicit buffer management during these situations led to substantial performance gains. In our case the code pattern that allowed us to leverage explicit buffer management looked like this: final double[] buffera = new double [SIZE]; final double[] bufferb = new double [SIZE]; Kernel kernel= new Kernel(){ // read/write buffera OR bufferb depending on some condition ; boolean somecondition = // set the value at run time for (int i=start; i < end; i++){ kernel.execute(i); if(somecondition){ // read buffera else { // read bufferb

Without explicit buffer management, buffera and bufferb got transferred from GPU to the host/cpu between every execution of the kernel in the for loop. Also both buffera and bufferb got transferred after the for loop irrespective of run-time value of somecondition variable. We could easily modify this to use explicit buffer management as (see bold faced text): final double[] buffera = new double [SIZE]; final double[] bufferb = new double [SIZE]; Kernel kernel= new Kernel(){ // read/write buffera OR bufferb depending on some condition ; kernel.setexplicit(true); // tells Aparapi that the user will // control buffer management for this kernel kernel.put(buffera); // transfer buffera to the OpenCL device kernel.put(bufferb); // transfer bufferb to the OpenCL device boolean somecondition = // set the value at run time // neither buffera nor bufferb will be transferred back to the host for (int i=start; i < end; i++){ kernel.execute(i); if(somecondition){ kernel.get(buffera); // get only buffera transferred from OpenCL device //read buffera else { kernel.get(bufferb); // get only bufferb transferred from OpenCL device //read bufferb This approach avoided unnecessary buffer transfers between successive executions of the kernel. It also allowed selective transfer of either of the buffers based on the runtime condition. For additional code patterns that could benefit from explicit buffer management and more details on this feature please see http://code.google.com/p/aparapi/wiki/newfeatures Another general suggestion on kernel tuning is to try to move host side computation logic between successive kernel executions in to the kernels itself. That way you make your kernels more compute intensive and you also don t have to cross GPU-CPU boundary as much as you would need to otherwise. Empirical Data We ran our sample application using the stock JQuantlib 0.2.3 release and compared it with our JQuantlib version that included changes for using Aparapi kernels. We call performance numbers collected using the stock JQuantlib version as Reference numbers (REF for short). We call performance numbers collected using modified JQuantlib version as GPU numbers.

Experimental Setup Our experiments were performed on a system with following hardware and software configuration: AMD Phenom II X6 1090T @ 3.2GHz processor 8 GB DDR3 1600MHz RAM MSI 890FXA-GD70 motherboard ATI Radeon HD 5870 graphics card Windows 7 Ultimate 32-bit OS JDK version 1.7.0 (b147 with HotSpot Server VM build 21.0-b17) Aparapi 2011_09_13 release AMD Catalyst version 11.9 AMD APP SDK v2.5 JQuantlib version 0.2.3 We looked at performance of all the different binomial option pricing methods namely Tian, Jarrow- Rudd, Trigeorgis, CoxRossRubinstein, AdditiveEQPBinomialTree, Joshi4 and LeisenReimer exercised by the original sample application. We exercised each of these option pricing methods for 5 iterations and took the average of the last 4 iterations for deriving their execution time. We exclude numbers from the first iteration to filter out class loading and JIT compilations noise on the Java side and Java bytecode to OpenCL conversion cost on the Aparapi side which however happens only prior to the first execution of the kernel. Table 1 and Table 2 contain execution time in milliseconds for different Binomial option pricing methods at different global size values. Table 1 contains data for global sizes 64 through 1024. Table 2 contains data for global sizes 2048 through 32768. The Performance Gains section of these tables show the speed-up achieved by GPU offload as compared to the REF performance. A ratio of less than 1 indicates that the REF performance was better than GPU performance. A ratio of 1 indicates that they were at par and a ratio of greater than 1 indicates the speed-up achieved by GPU offload. The Global Size here indicates the number of steps used by the option pricing algorithms. The number of steps maps to the granularity at which the upward and downward movement of options are computed. As you can see in Table 1 and Table 2, as the global size increases GPU offload starts to outperform reference implementation. This is because the gains achieved by GPU offload starts to overcome the cost of data transfer between the CPU and the GPU. On some of the option pricing algorithms a speedup of as much as 20x was achieved using Aparapi. Also, GPU starts to outperform REF implementation at global sizes as small as 512. Note that for smaller problem sizes one could continue using reference implementation since it is possible to toggle Aparapi kernel usage by means of user defined run-time flags of the JVM process.

Note: In some cases we saw REF implementation taking 0 milliseconds. We have replaced those data points with 0.1 millisecond instead to get meaningful performance difference numbers. There are 5 such instances and all of them are in Table 1. REF 64 GPU 64 REF 128 GPU 128 REF 256 GPU 256 REF 512 GPU 512 REF 1K GPU 1K Tian 3.75 7.75 7.75 11.75 19.5 23.25 66.25 50.75 249.75 101.3 Jarrow-Rudd 0.1 8 4 11.75 3.75 19.5 15.5 46.75 50.5 93.5 Trigeorgis 4 4 4 11.75 7.75 19.5 15.5 47 50.75 89.75 CoxRossRubinstein 0.1 7.75 3.75 11.5 0.1 23.5 15.75 43 50.75 89.75 AdditiveEQPBinomialTree 0.1 3.75 0.1 11.75 3.75 23.25 15.5 46.75 47 89.75 Joshi4 3.75 7.75 7.75 11.75 15.75 23.5 66.25 46.75 245.75 97.5 LeisenReimer 4 7.75 7.75 15.75 15.5 27.5 66.25 46.75 249.75 101.3 Performance Gains Global Size 64 128 256 512 1K Tian 0.484 0.660 0.839 1.305 2.465 Jarrow-Rudd 0.013 0.340 0.192 0.332 0.540 Trigeorgis 1.000 0.340 0.397 0.330 0.565 CoxRossRubinstein 0.013 0.326 0.004 0.366 0.565 AdditiveEQPBinomialTree 0.027 0.009 0.161 0.332 0.524 Joshi4 0.484 0.660 0.670 1.417 2.521 LeisenReimer 0.516 0.492 0.564 1.417 2.465 Table 1: Comparison of REF and GPU execution time for global sizes 64 through 1024 REF 2K GPU 2K REF 4K GPU 4K REF 8K GPU 8K REF 16K GPU 16K REF 32K GPU 32K Tian 967.25 217.00 3872.75 511.00 15713.00 1299.00 64266.30 4006.00 262902.00 13357.75 Jarrow-Rudd 171.75 179.50 670.75 538.75 2718.50 768.25 10970.80 1954.00 43785.50 5179.00 Trigeorgis 171.75 179.30 674.75 355.00 2707.75 768.50 10970.80 1942.00 43876.00 5109.00 CoxRossRubinstein 175.50 179.50 667.00 358.80 2749.75 768.30 11022.30 1954.00 44759.50 5109.00 AdditiveEQPBinomialTree 175.50 179.50 670.75 359.00 2710.75 768.30 11177.80 1958.00 44664.00 5172.00 Joshi4 971.25 210.50 3872.75 511.00 15705.50 1299.00 64187.00 4388.00 265004.00 13685.00 LeisenReimer 963.25 210.50 3868.75 511.00 15725.00 1299.00 64245.50 3787.00 263657.00 15355.00 Performance Gains Global Size --> 2K 4K 8K 16K 32K Tian 4.457 7.579 12.096 16.043 19.682 Jarrow-Rudd 0.957 1.245 3.539 5.615 8.454 Trigeorgis 0.958 1.901 3.523 5.649 8.588 CoxRossRubinstein 0.978 1.859 3.579 5.641 8.761 AdditiveEQPBinomialTree 0.978 1.868 3.528 5.709 8.636 Joshi4 4.614 7.579 12.090 14.628 19.365 LeisenReimer 4.576 7.571 12.105 16.965 17.171 Table 2: Comparison of REF and GPU execution time for global sizes 2048 through 32768

Figure 1 and Figure 2 show performance difference between REF and GPU implementation for Tian option pricing type. 250 Tian 249.75 Execution time in milliseconds 200 150 100 50 66.25 50.75 101.3 REF GPU 0 3.75 64 7.75 19.5 23.25 11.75 7.75 128 256 512 1024 Global Size Figure 1: Performance comparison between REF and GPU mode of Tian method for global sizes 64 through 1024

Tian 300000.00 262902.00 250000.00 Execution time in milliseconds 200000.00 150000.00 100000.00 64266.30 REF GPU 50000.00 0.00 15713.00 13357.75 1299.00 4006.00 2048 4096 8192 Global Size Figure 2: Performance e comparison between REF and GPU mode of Tian method for global sizes 2048 through 32768 Conclusion Aparapi is a simple and a very effective mechanism of leveraging GPU compute resources for general purpose Java computations. If you have an application that t is data parallel in nature then it is worthwhile to investigate how you could integrate it with Aparapi framework. Using Aparapi we were able to speed up a sample Java based financial application by as much as 20x. We hope that this article has provided you with sufficient directions that you could use to get started with integrating Aparapi in to your applications. Acknowledgements We would like to thank Vincent Hindriksen, CEO of StreamComputing for providing us insightful comments and suggestions on this article. We would also like to thank the whole Runtimes group at AMD, Austin for their comments and suggestions. Appendix A Appendix A contains source code for the sample application that was used for performance measurements noted in Table 1 and Table 2.

/* Copyright (C) 2007 Richard Gomes This source code is release under the BSD License. This file is part of JQuantLib, a free-software/open-source library for financial quantitative analysts and developers - http://jquantlib.org/ JQuantLib is free software: you can redistribute it and/or modify it under the terms of the JQuantLib license. You should have received a copy of the license along with this program; if not, please email <jquant-devel@lists.sourceforge.net>. The license is also available online at <http://www.jquantlib.org/index.php/license.txt>. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. JQuantLib is based on QuantLib. http://quantlib.org/ When applicable, the original copyright notice follows this notice. */ /* Copyright (C) 2005, 2006, 2007 StatPro Italia srl This file is part of QuantLib, a free-software/open-source library for financial quantitative analysts and developers - http://quantlib.org/ QuantLib is free software: you can redistribute it and/or modify it under the terms of the QuantLib license. You should have received a copy of the license along with this program; if not, please email <quantlib-dev@lists.sf.net>. The license is also available online at <http://quantlib.org/license.shtml>. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the license for more details. */ package org.jquantlib.samples; import org.jquantlib.settings; import org.jquantlib.daycounters.actual365fixed; import org.jquantlib.daycounters.daycounter; import org.jquantlib.exercise.americanexercise; import org.jquantlib.exercise.exercise; import org.jquantlib.instruments.option; import org.jquantlib.instruments.payoff; import org.jquantlib.instruments.plainvanillapayoff; import org.jquantlib.instruments.vanillaoption; import org.jquantlib.methods.lattices.additiveeqpbinomialtree; import org.jquantlib.methods.lattices.coxrossrubinstein; import org.jquantlib.methods.lattices.jarrowrudd; import org.jquantlib.methods.lattices.joshi4;

import org.jquantlib.methods.lattices.leisenreimer; import org.jquantlib.methods.lattices.tian; import org.jquantlib.methods.lattices.trigeorgis; import org.jquantlib.pricingengines.vanilla.binomialvanillaengine; import org.jquantlib.processes.blackscholesmertonprocess; import org.jquantlib.quotes.handle; import org.jquantlib.quotes.quote; import org.jquantlib.quotes.simplequote; import org.jquantlib.termstructures.blackvoltermstructure; import org.jquantlib.termstructures.yieldtermstructure; import org.jquantlib.termstructures.volatilities.blackconstantvol; import org.jquantlib.termstructures.yieldcurves.flatforward; import org.jquantlib.time.calendar; import org.jquantlib.time.date; import org.jquantlib.time.month; import org.jquantlib.time.calendars.target; /** * Calculates equity option values with a number of methods * * @see http://quantlib.org/reference/_equity_option_8cpp-example.html * * @author Richard Gomes * @author Shrinivas Joshi */ public class AparapiEquityOptions implements Runnable{ public static void main(final String[] args) { new AparapiEquityOptions().run(); public void run() { double anpv = Double.NaN; // set up dates final Calendar calendar = new Target(); final Date todaysdate = new Date(15, Month.May, 1998); final Date settlementdate = new Date(17, Month.May, 1998); new Settings().setEvaluationDate(todaysDate); // our options final Option.Type type = Option.Type.Put; final double strike = 40.0; final double underlying = 36.0; /* @Rate */final double riskfreerate = 0.06; final double volatility = 0.2; final double dividendyield = 0.00; final Date maturity = new Date(17, Month.May, 1999); final DayCounter daycounter = new Actual365Fixed(); // Define exercise for American Options final Exercise americanexercise = new AmericanExercise(settlementDate, maturity); // bootstrap the yield/dividend/volatility curves

final Handle<Quote> underlyingh = new Handle<Quote>(new SimpleQuote(underlying)); final Handle<YieldTermStructure> flatdividendts = new Handle<YieldTermStructure>(new FlatForward(settlementDate, dividendyield, daycounter)); final Handle<YieldTermStructure> flattermstructure = new Handle<YieldTermStructure>(new FlatForward(settlementDate, riskfreerate, daycounter)); final Handle<BlackVolTermStructure> flatvolts = new Handle<BlackVolTermStructure>(new BlackConstantVol(settlementDate, calendar, volatility, daycounter)); final Payoff payoff = new PlainVanillaPayoff(type, strike); final BlackScholesMertonProcess bsmprocess = new BlackScholesMertonProcess(underlyingH, flatdividendts, flattermstructure, flatvolts); // American Options final VanillaOption americanoption = new VanillaOption(payoff, americanexercise); // Analytic formulas: int timesteps = Integer.getInteger("numSteps", 256); int iterations = Integer.getInteger("iterations", 10); System.out.println("Number of iterations " + iterations); long itertime; if (Boolean.getBoolean("JarrowRudd")) { System.out.println("Binomial Jarrow-Rudd"); for (int i = 0; i < iterations; i++) { // Binomial JarrowRudd americanoption.setpricingengine(new BinomialVanillaEngine<JarrowRudd>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial Jarrow-Rudd Iteration " + (i + 1) + " took " + (System.currentTimeMillis() - itertime)); System.out.println("American option " + anpv); if (Boolean.getBoolean("Tian")) { System.out.println("Binomial Tian"); for (int i = 0; i < iterations; i++) { // Binomial Tian americanoption.setpricingengine(new BinomialVanillaEngine<Tian>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial Tian Iteration " + (i + 1) + " took " + (System.currentTimeMillis() - itertime));

System.out.println("American option " + anpv); if (Boolean.getBoolean("Joshi4")) { System.out.println("Binomial Joshi4"); for (int i = 0; i < iterations; i++) { // Binomial Joshi4 americanoption.setpricingengine(new BinomialVanillaEngine<Joshi4>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial Joshi4 Iteration " + (i + 1) + " took " + (System.currentTimeMillis() - itertime)); System.out.println("American option " + anpv); if (Boolean.getBoolean("LeisenReimer")) { System.out.println("Binomial LeisenReimer"); for (int i = 0; i < iterations; i++) { // Binomial LeisenReimer americanoption.setpricingengine(new BinomialVanillaEngine<LeisenReimer>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial LeisenReimer Iteration " + (i + 1) + " took " + (System.currentTimeMillis() - itertime)); System.out.println("American option " + anpv); if (Boolean.getBoolean("Trigeorgis")) { System.out.println("Binomial Trigeorgis"); for (int i = 0; i < iterations; i++) { // Binomial Trigeorgis americanoption.setpricingengine(new BinomialVanillaEngine<Trigeorgis>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial Trigeorgis Iteration " + (i + 1) + " took " + (System.currentTimeMillis() - itertime)); System.out.println("American option " + anpv); if (Boolean.getBoolean("CoxRossRubinstein")) { System.out.println("Binomial CoxRossRubinstein"); for (int i = 0; i < iterations; i++) { // Binomial CoxRossRubinstein americanoption.setpricingengine(new BinomialVanillaEngine<CoxRossRubinstein>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial CoxRossRubinstein Iteration " + (i + 1) + " took "

+ (System.currentTimeMillis() - itertime)); System.out.println("American option " + anpv); if (Boolean.getBoolean("AdditiveEQPBinomialTree")) { System.out.println("Binomial AdditiveEQPBinomialTree"); for (int i = 0; i < iterations; i++) { // Binomial AdditiveEQPBinomialTree americanoption.setpricingengine(new BinomialVanillaEngine<AdditiveEQPBinomialTree>(bsmProcess, timesteps){ ); itertime = System.currentTimeMillis(); anpv = americanoption.npv(); System.out.println("Binomial AdditiveEQPBinomialTree Iteration " + (i + 1) + " took " + (System.currentTimeMillis() - itertime)); System.out.println("American option " + anpv); 2011 Advanced Micro Devices, Inc. All rights reserved. AMD, the AMD arrow logo, AMD Phenom, Radeon, and combinations thereof are trademarks of Advanced Micro Devices, Inc. Windows is a registered trademark of Microsoft Corporation in the United States and/or other countries. OpenCL is a trademark of Apple Inc., used with permission by Khronos. All other names used in this article are for informational purposes only and may be trademarks of their respective owners.