Analytics and knowledge extraction on graph data structures have become areas of great interest in today’s large-scale datacenters, as social network analysis and machine learning applications have received considerable attention. Many software systems(Malewicz et al., 2010; Low et al., 2010, 2012; Zhu et al., 2016) have been developed for this application domain. Graph algorithms are usually embarrassingly parallel, which makes massively parallel accelerators (e.g. GPUs) possible candidates to speedup them. However, efficient graph processing on GPUs is challenging because 1) graph algorithms are irregular and 2) GPUs have highly structured architecture. It is difficult to avoid inefficiency when mapping irregular algorithms to a structured architecture. Specifically there are four major problems for GPU graph processing: (1) low work efficiency, (2) high synchronization overhead, (3) load imbalance and (4) poor data locality.
Lots of previous work have contributed to alleviate the effects of the first three problems, but the poor locality problem has got little attention on GPUs. This is because 1) with thousands of threads running simultaneously, GPUs have relatively much smaller cache capacity per thread compared to CPUs, which makes cache oriented optimization techniques less effective on GPUs, 2) GPU memory hierarchy is quite different from that of CPU, which makes CPU cache optimization techniques not directly applicable to the GPU, 3) GPU L2 cache is usually used as a global synchronization point instead of holding the working set, and GPU programmers tend to use scratchpad for locality issues, and 4) it is hard to capture locality from random accesses in graph algorithms. Fortunately, recent GPUs have shown a trend of including larger caches on chip, making cache oriented optimization a more important role in achieving highly efficient graph processing on GPUs.
In this work, we focus on improving cache performance for graph processing on GPUs. Graph algorithms have poor cache utilization on GPUs due to the random memory accesses to the vertex values. When the graph is large enough, random accesses cause frequent cache misses and long memory access latency. Cache blocking is an optimization technique to improve locality by partitioning a data structure into small blocks such that each block can fit in cache. CuSha (Khorasani et al., 2014) is a GPU graph processing framework that uses the idea of cache blocking. It partitions input graphs into Shards to fit into the GPU shared memory. This approach leverages fast shared memory in GPUs, but limited by shared memory size it produces too many subgraphs which leads to non-trivial overhead. Meanwhile, CuSha uses a COO-like graph representation that requires roughly 2.5 global memory space than the typical CSR format. To overcome the limitations, we propose to leverage the last level cache (LLC) in the GPU to improve locality. Meanwhile, the graph representation is based on CSR to save global memory space.
However, naively applying cache blocking on LLC can bring a large volume of inefficient memory accesses on the GPU. In this paper, we design a static cache blocking scheme for both pull and push directions. Our scheme replaces sparse accesses to the global sums with dense accesses to partial sums, which improves memory access efficiency. We also coordinate our optimization with state-of-the-art load balancing. For traversal-based graph algorithms, we consider the iterations in different phases according to their dynamic change of the working set size, and apply our cache blocking scheme for topology-driven kernels in pull direction. We implement a graph processing framework GraphCage that integrates our proposed optimizations. We evaluate GraphCage on a recent NVIDIA GPU with diverse real-world graphs. Experimental results show that GraphCage achieves speedup of 2 over hand optimized implementations, and 4 over the implementations in state-of-the-art GPU graph processing frameworks Gunrock. Compared to a previous GPU graph partitioning framework, CuSha, GraphCage achieves better performance with less memory consumption.
This paper makes the following contributions:
We investigate the cache performance of state-of-the-art graph algorithms on GPUs and observe poor cache utilization caused by random memory accesses.
We propose GraphCage, a cache aware optimization framework for efficient GPU graph processing. The core is a static cache blocking scheme specialized for the GPU.
We apply our cache optimization with load balancing, and enable it for traversal-based algorithms, to achieve high performance for different applications.
We measure GraphCage on the NVIDIA GPU and show superior performance over state-of-the-art implementations.
2. Background and Motivation
Recently people have intensively investigated many graph algorithms on various platforms, and proposed plenty of parallel strategies and optimization techniques to make them as fast as possible. Meanwhile, many graph processing frameworks and libraries have been developed to improve the performance and programmability of graph applications targeting CPUs (Shun and Blelloch, 2013; Nguyen et al., 2013; Sundaram et al., 2015; Sengupta et al., 2015; Anderson et al., 2016) and GPUs (Wang et al., 2016; Fu et al., 2014; Zhong and He, 2014), by generalizing these proposed optimizations. In this section, we first summarize these strategies and optimizations. We then point out that there is still room for performance improvement even if these sophisticated optimizations have been applied. Finally we briefly introduce cache blocking, and show its limitations when applied to GPU graph processing.
2.1. Graph Processing on GPUs
Efficient graph processing on GPUs is notoriously difficult. There has been considerable recent effort in improving the performance of various graphs algorithms on both CPUs and GPUs. Generally, there are four key issues that affect the performance most: 1) work efficiency (Beamer et al., 2012; Nasre et al., 2013; Luo et al., 2010; Hong et al., 2011; Grossman et al., 2018; Sutton et al., 2018), 2) load balancing (Merrill et al., 2012; Davidson et al., 2014; Sun et al., 2017; Liu and Huang, 2015), 3) synchronization overhead (Merrill et al., 2012; Besta et al., 2017), and 4) data locality (Beamer et al., 2015b; Zhang et al., 2017; Beamer et al., 2017; Buono et al., 2016). Both algorithm-level and architecture-oriented optimization techniques have been proposed targeting one or several of these issues. In this section, we briefly summarize these methods and explain how they are applied on GPUs. We use Fig. 1 as an example to illustrate the basic ideas. Fig. 1 (a) is a directed graph whose CSR format is shown in Fig. 1 (b).
We use vertex-centric model (Pingali et al., 2011) in this work. There are two kinds of graph algorithms: 1) All-active: all the vertices are active in every iteration, i.e. PageRank. 2) Partial-active: only some of the vertices are active in each iteration and the active vertex set is changing during the execution, i.e. BFS. The algorithm converges when the active vertex set is empty. Many graph traversal based algorithms are partial-active, and the frontier queue in the graph traversal is the active vertex set. Optimizations used for all-active and partial-active algorithms may be different because the active vertex set could substantially change the program characteristics.
Work Efficiency. It is important to improve work efficiency on a parallel machine (especially on a massively parallel processor) to take advantage of its compute capability. Work efficiency highly depends on how the threads are mapped to the tasks. On GPUs, usually two mapping strategies are utilized: topology-driven or data-driven, to map the work to the threads (Nasre et al., 2013). As shown in Fig. 2 (a), the topology-driven scheme assigns a thread to each vertex in the graph, and in each iteration, the thread stays idle or processes a vertex depending on whether its corresponding vertex is active or not. It is straightforward to implement the topology-driven scheme on GPU with no extra data structure. By contrast, the data-driven scheme maintains an active vertex queue which holds the vertices to be processed during a certain iteration. In this iteration, threads are created in proportion to the size of the queue. Each thread is responsible for processing a certain amount of vertices in the queue and no thread is idle, as shown in Fig. 2 (b). Therefore, the data-driven scheme is generally more work-efficient than the topology-driven one when a small portion of the vertices are active, but it needs extra overhead to maintain the queue. Mapping strategies usually work with the direction optimization (Beamer et al., 2012) technique to improve work efficiency. Graph algorithms can be implemented in either pull or push direction. Fig. 1 (c) illustrates the traversal order when performing breadth first search (BFS) on the graph in Fig. 1 (a). In level 2 we apply push direction with either a frontier queue (Fig. 1 (d)) or a status array (Fig. 1 (e)). In level 3 pull direction is used (Fig. 1 (f)). This hybrid method dynamically changes the direction to maximize work efficiency for each iteration (level).
Load Balancing. Since vertices have different amount of edges to be processed by the corresponding threads, load imbalance becomes an important issue for vertex-centric graph processing, particularly when the input is a scale-free graph in which neighbor sizes of vertices can differ by orders of magnitude. Hong et al. (Hong et al., 2011) proposed a Virtual Warp Centric (VWC) method for BFS to map warps rather than threads to vertices. Since the edges of a vertex are stored continuously, this approach can not only balance the work inside the warp, but also lead to coalesced memory accesses, which improves bandwidth efficiency. As an extension, Merrill et al. (Merrill et al., 2012) proposed a hierarchical load balancing strategy Thread+warp+CTA (TWC) which maps the workload of a single vertex to a thread, a warp, or a thread block, according to the size of its neighbor list. At the fine-grained level, all the neighbor list offsets in the same thread block are loaded on chip, then the threads in the block cooperatively process per-edge operations. At the coarse-grained level, per-block and per-warp schemes are utilized to handle the extreme cases: (1) neighbor lists larger than a thread block; (2) neighbor lists larger than a warp but smaller than a thread block respectively. Fig. 2 (c) shows a load balanced data-driven mapping, where each thread is assigned to process an edge. In this way, the TWC approach can handle vertices with different sizes of neighbor lists. Enterprise (Liu and Huang, 2015) further improves TWC with inter-block load balancing for handling extremely high-degree vertices (hub vertices) using the entire grid.
Synchronization overhead. Updating vertex values in parallel may cause conflicts, which requires atomic operations to guarantee correctness. Usually pull-style operations do not need atomics since vertex values are accumulated by individual threads separately. However, conflicts happen for push-style operations as different threads could write to the same vertex value concurrently. Therefore, for those all-active algorithms, such as PR, pull is usually faster than push as it requires no atomic operation. But when we apply fine-grained load balancing scheme, such as TWC, to pull-style algorithms, conflicts occur among cooperative threads in the thread block, and thus it brings in atomic operations. This leads to significant synchronization overhead which is not acceptable. So usually we only apply coarse-grained load balancing in pull direction. On the contrary, TWC works well for push-style operations as synchronization can not be avoided no matter what strategy is used. Besides, maintaining the frontier queue in partial-active algorithms also requires synchronization (Merrill et al., 2012).
Data locality. Most of the above mentioned optimizations have been incorporated in state-of-the-art graph processing frameworks such as Gunrock (Wang et al., 2016). Therefore, graph applications written within the Gunrock framework can achieve excellent performance close to that of hand-optimized implementations. By contrast, CuSha (Khorasani et al., 2014) is a framework mainly focusing on partitioning the input graph into shards and therefore can coalesce memory accesses and make good use of the scratchpad. This method is very similar to the propagation blocking technique used in CPU graph algorithms (Beamer et al., 2017; Buono et al., 2016). CuSha can also improve performance compared to non-partitioned implementations. However, since the subgraph size is confined by the size of scratchpad in each SM, each subgraph is small and there are too many blocks generated. This leads to non-trivial overhead on merging partial results of subgraphs.
2.2. Performance Bottleneck on GPUs
The parallel strategies and optimization techniques introduced above have substantially improved the performance of graph processing on GPUs (Wang et al., 2016). However, the GPU hardware is still poorly utilized. We conduct a characterization on the GPU using hand optimized PageRank. We observe low hardware utilization, including both the compute units and memory bandwidth. This result indicates that the performance is still limited by memory access latency, even if state-of-the-art optimizations are applied.
The inefficiency of the memory hierarchy is due to the large volume of the input data and the irregular memory access pattern of the data. When executed on a GPU, this large volume of irregular memory accesses can not be coalesced. They are translated into a huge amount of load and store transactions in the memory hierarchy. Demand-fetched caches in GPUs have very limited capacity and are impossible to hold this huge amount of data on chip (Jia et al., 2012). This causes the cache thrashing problem (Chen et al., 2014), leading to a large number of DRAM accesses. Therefore graph algorithms exhibit very poor temporal and spatial locality, although there is plenty of potential data reuses exist in these algorithms (Beamer et al., 2015b). Software managed local memory (or scratchpad) is also not capable to capture the data reuses for these random accesses. On the other hand, since most of the memory requests are sent to DRAM and cause significant memory access latency, the SM pipelines are often stalled waiting for the return of memory requests, which leads to inefficiency of the compute units as well.
To understand the performance bottleneck more specifically, we use PageRank (PR) as an example. PR (Page et al., 1998) is a graph algorithm for ranking a website based on the score (rank) of the sites that link to it. PR is iterative; at each iteration, it updates the score of each vertex using the weighted sum of its neighbors’ scores and degrees. PR can be implemented in either pull or push direction. Algorithm 1 shows one iteration of pull-based PR (PR-pull), while Algorithm 2 is in push style (PR-push). For each destination vertex, PR-pull gathers contributions from all the incoming neighbors (line 6 & 7) and accumulate them to sums (line 8). On the contrary, PR-push scatters the contribution of each source vertex to all its outgoing neighbors (line 6). Therefore, when implemented in parallel, atomic operations are required (line 7).
For both algorithms, lines from 4 to 7 are the most important, which consume most of the execution time. PR-pull avoids atomic operations, and the sum of incoming contributions will have high locality. But the random memory accesses to the contributions of source vertices (line 7 in Algorithm 1) could have low locality and become the major performance limiter. PR-push requires atomic operations, and therefore is usually slower than PR-pull. In push style, the outgoing contribution will have high locality, but reading (and writing) its neighbors’ sums (destination vertices) could have low locality (line 7 in Algorithm 2), which also causes significant memory latency and limits overall performance.
We use another example, Betweenness Centrality (BC) (McLaughlin and Bader, 2014), to show the characteristics of traversal based algorithms such as BFS and SSSP. BC is commonly used in social network analysis to measure the influence a vertex has on a graph. It calculates a betweenness centrality score for each vertex in a graph based on shortest paths. For every pair of vertices in a connected graph there exists at least one shortest path between them. A vertex’s betweenness centrality score is related to the fraction of shortest paths between all vertices that pass through the vertex. A push style data-driven shortest path calculation kernel in BC is shown in Algorithm 3, where is the depth and is the number of shortest paths for each vertex. The parallel for loop in Line 2 assigns one thread to each element in the frontier queue such that only active vertices in each iteration are traversed. The atomic Compare and Swap (CAS) operation on Line 4 is used to prevent multiple insertions of the same vertex into . Since this kernel is in push style, it requires atomics to update (line 7). In this kernel, random accesses to and of the destination vertices cause cache thrashing.
2.3. Cache Blocking
Although random accesses can not be avoided anyway, there is still plenty of room for performance improvement if we can make good use of caches by reordering the memory accesses. Cache blocking (Nishtala et al., 2007; Buluç et al., 2009) is such a reordering technique proposed to improve cache performance for dense linear algebra and other applications. By partitioning the graph into small blocks, the range of vertex values in each subgraph is reduced significantly such that the corresponding the arrays data for vertex values are small enough to reside in cache, therefore improving the locality (Beamer et al., 2017). Cache blocking can be done in 1D or 2D, and can be applied to either push or pull direction. Although cache blocking can significantly improve performance for dense matrix computation, naively applying cache blocking to graph processing on GPUs is much less effective. Take PR-pull as an example. Cache blocking for PR-pull divides the adjacency matrix into column blocks. This will improve the locality of reading the neighbors’ contributions (line 7 in Algorithm 1), as the source vertex id (the index of the contributions array) is restricted in the partition, and therefore the corresponding vertex values can fit in cache. But column blocking results in repeated accesses to sums (line 8 in Algorithm 1) for each block. In another word, cache blocking reduces accesses to one data structure at the cost of increasing accesses to another data structure.
Actually, in each subgraph, the number of edges is much fewer than that in the original graph. Many destination vertices may not have edge in a given subgraph since source vertices of this subgraph are restricted in a small range. For these vertices, accessing sums is useless. However, for GPU implementations, even if there is only one thread in a warp has updated sums, there will be a memory write request generated for this update. This memory access pattern is inefficient spatially. When the graph is not very large and it is partitioned into only several subgraphs, these inefficient memory accesses may not be a problem. But when a graph is large enough to have tens of subgraphs or more, this overhead becomes non-trivial and cache blocking achieves only marginal performance improvement or could even lead to slowdowns.
3. GraphCage Design
In this section, we describe our GraphCage design in a progressive way. We first propose a throughput-oriented cache blocking (TOCAB) scheme which is orchestrated specifically for the GPU architecture. TOCAB is applicable for both pull and push style algorithms. We then integrate TOCAB with load balancing to achieve high performance. Finally, we enable cache blocking for traversal based algorithms by considering the change of working set size.
3.1. Throughput Oriented Cache Blocking
As aforementioned, conventional cache blocking for CPUs is much less effective for GPUs. To enable efficient cache blocking on GPUs, we propose Throughput Oriented Cache Blocking (TOCAB) that is designed for the GPU architecture. At the beginning, we have several design choices to make. First, although modern GPUs have multiple levels of caches with different types (e.g. read-only cache and software managed scratchpad memory), in this work our cache blocking scheme mainly focuses on the last-level cache (LLC), i.e. the L2 cache in most of the modern GPU architectures. This is reasonable because 1) scratchpads or read-only caches are level-one caches with relatively small capacities, which severely limits the block size of cache blocking; 2) the latency and bandwidth between the LLC and DRAM is typically the performance bottleneck and we are committed to reduce the overall number of DRAM accesses. Note that scratchpad and read-only caches are also useful and we will discuss how to leverage them for locality later.
Second, we apply one dimensional (1D) instead of two dimensional (2D) blocking. This is because 1) transforming the CSR data structure is easier for 1D blocking, which means less preprocessing overhead; 2) 2D blocking partitions the graph on both source and destination vertices, which may generate too many small blocks in total. Smaller blocks result in fewer data reuses to capture and less parallelism for the GPU to exploit, while more blocks lead to more overhead for merging the partial results of each block. When there are too many blocks, the overhead may outweigh the benefit of cache blocking and therefore leads to limited performance improvement or even slowdown. In this case, the block size is actually a tradeoff between locality benefit and blocking overhead. To obtain a scalable block size, we decide to use 1D blocking.
Third, we choose to use static blocking instead of dynamic blocking. Static blocking divides the graph into subgraphs before the execution of the algorithm, and therefore incurs less runtime overhead. By contrary, dynamic blocking (Beamer et al., 2017; Kiriansky et al., 2016) uses intermediate buffers to hold the data at runtime, and the data is dynamically distributed into different buffers so that each buffer can fit in cache. The intermediate data is processed separately and then the partial results are accumulated. Dynamic blocking requires no or little modification on the original CSR data structure, but needs a huge memory space for the intermediate buffers. In addition to consuming more memory, dynamic blocking performs additional load and store instructions in order to insert data into the buffers and read data back from the buffers. Note that preprocessing can be applied to reduce this runtime overhead of dynamic blocking, which is a tradeoff between static and dynamic overhead. Anyway, preprocessing (static overhead) is necessary to ensure high performance for both static and dynamic approaches. We choose static blocking to reduce as much dynamic overhead as possible, because 1) most of graph algorithms are iterative and take a large number of iterations, making a case for additional preprocessing time; 2) preprocessing cost is small compared to the significant performance gains; 3) the partitioned graphs can also be reused across multiple graph applications, further amortizing the preprocessing cost; 4) the dynamic operations are simpler after the graph is statically blocked, making it easier to implement on GPUs for different graph applications.
Based on these design choices, TOCAB can be divided into three phases. First, the original CSR data structure is modified to store the subgraphs, i.e. the preprocessing phase. Second, each subgraph is processed sequentially, i.e. the subgraph processing phase. Third, the partial results of all subgraphs are reduced to the final results, i.e. the post-processing (reduction) phase. We describe each phase in details as follows.
In the preprocessing phase, we partition the graph into subgraphs so that the vertex values in each subgraph can fit in cache. One technique is to store the subgraphs each as their own graph, which is known as blocked CSR (as shown in Fig. 3). For each subgraph, it has its own CSR data structures (e.g. and
). Computationally, it is easy to generate blocked CSR for each subgraph. For pull model, we do column blocking which takes each edge in the graph and classifies it to the subgraph which its source vertex belongs to. For push model, destination vertex is used instead of source vertex, and therefore we apply row blocking. Note that the code for column blocking (pull) can be reused for row blocking (push), since the input graph of the push model is just the transpose graph of that used in the pull model. This means the same preprocessing code works for both push and pull models with no modification.
However, blocked CSR is not enough to achieve high performance. As mentioned, for pull model, the gain of cache blocking for the low-locality access stream (contributions) comes at the expense of the high-locality access stream (sums). It is critical to reduce the overhead of repeatedly accessing sums. Since only a small portion of destination vertices have incoming edges in each subgraph, to improve efficiency, we ignore the destination vertices with no edges and assign each destination vertices with at least one edge a local-ID. We keep a mapping of this local-IDs in the subgraph to the global-IDs used in the original graph, so that we can always find the global-ID of a vertex when we need to accumulate its partial results. We keep a partial_sums array for each subgraph. The partial results are not directly written into sums, but stored in partial_sums. After all the subgraphs are processed, all the partial_sums are reduced to get the correct sums. Since we use local-IDs instead of global-IDs for destination vertices, the partial_sums accessed by destination vertices in a given subgraph are stored contiguously in memory regardless of their global-IDs. This is particularly important for GPUs as we can have coalesced memory accesses for the partial_sums. Fig. 4 illustrate how the graph in Fig. 1 is partitioned into two subgraphs and the global-IDs are mapped to local-IDs for the destination vertices.
With this global to local ID transformation, the subgraph processing phase requires little modification on the original pull or push operations. Algorithm 4 and 5 show the operations to process a subgraph in pull and push style respectively. In Algorithm 4, destination vertices use local-ID so that accesses to partial_sums are coalesced (line 6). In Algorithm 5, accesses to contributions use global-ID (line 4), and thus we use the id_map to convert local-IDs into global-IDs (line 3). Since we confine the destination vertices in a small range, the atomic operations on sums happen in the cache (line 6), which substantially speedup the entire phase.
In the accumulation phase, only the pull-style implementation needs to reduce the partial results, because in push direction the contributions are already accumulated into sums as shown in Algorithm 4 line 6. We use a GPU friendly approach for accumulation, as shown in Fig. 5. Since we use local-IDs in the partial results, we need to find the global IDs and accumulate the values to the global results (sums). If different partial results are assigned to different threads, the write accesses will be inefficient, because the global IDs are not consecutive. Our strategy is to divide vertices into equal sized ranges (e.g. 1024), and assign the work of accumulating global results in each range to a thread block. A thread block is responsible to collect data from the specific range of all the subgraphs, and accumulate them in the shared memory. When all the partial results are reduced, the final results of this range are written back to the corresponding position of global memory. In this case, most of the read and write operations happen in the shared memory, while the reads from (partial_sums) and writes to (sums) the global memory are fully coalesced.
3.2. Coordinated Locality and Load Balancing
TOCAB improves memory access efficiency so that we have a cache blocking scheme that works efficiently on GPUs. To achieve high performance, we must integrate TOCAB with other optimization techniques. When we apply TOCAB with load balancing techniques, we find that cache blocking actually affect the load balancing strategy. As aforementioned, for PR-push it is easy to apply fine-grained load balancing, since atomic operations are not avoidable in push direction. However, we only apply coarse-grained load balancing (e.g. VWC) in pull direction to avoid atomics. Our experiment (not shown in the paper) finds that VWC works well for dense-frontier algorithms (e.g PR and SpMV) when the average degree is large (e.g. ), because the memory accesses are consecutive and coalesced, and the SIMD lanes are computationally efficient as most of the threads in a warp are working. However, when we partition the graphs into small subgraphs, we find that VWC no longer improves performance when applied to each subgraph. In fact, the average degree of vertices in the subgraphs are smaller than that in the original graph. Table 1 lists the degree distribution of LiveJournal before and after partitioning. In subgraphs more than 90% of the vertices have less than 8 edges and only %3.5 of the vertices have 16 or more edges. Since VWC gets each warp to process one vertex and most of the SIMD lanes in a warp are idle in this case, making VWC inefficient.
To coordinate cache blocking with load balancing, we apply different load balancing strategies in different directions. For the push-style case, whether or not the inner loop is parallelized, atomics are required. So we simply apply TWC in push direction. However, for the pull pattern, parallelizing the inner loop leads to synchronization overhead (Grossman et al., 2018). VWC is also not suitable due to its low SIMD efficiency. Therefore, we only apply coarse-grained load balancing in pull direction. This is a rational choice because hub vertices are still processed by thread blocks or warps, while low-degree vertices are processed in serial since in most cases they have only a small number of edges and may not be the performance bottleneck. For some graphs whose performance highly relies on fine-grained load balancing, using this approach may cause some performance loss. But our evaluation shows that in general it achieves very good performance for most of the graphs.
3.3. Work Efficient Cache Optimization
We have discussed our approach for all-active graph algorithms such as PR. However, there are many partial-active graph algorithms, for example, traversal based graph algorithms, such as BFS, BC and SSSP. These algorithms have different characteristics. In each iteration the operator may update the values of only a small subset of vertices. For example, in the first iteration of BFS, only the neighbors of the source vertex have their values updated. Therefore data-driven mapping with a frontier queue is used to avoid unnecessary updates. These differences introduce two extra issues when considering cache blocking: 1) the maintenance of the frontier; 2) the change of working set size.
For a data-driven implementation, when a graph is partitioned into subgraphs, extra operations are required to synchronize the frontier queue, which could be expensive. Another approach is to use topology-driven mapping with status arrays (size of ) instead of queues, as shown in Fig. 1 (e). Array front is used to show whether the vertices are in the current frontier or not. Array next is used to label the vertices in the frontier of the next level. When we partition the graph, we assign each subgraph a local next array. When processing each subgraph, new items are pushed into local status array. After all subgraphs are processed, the local status array are reduced into a global status array. This is a similar approach used for the partial_sum in PR. So we can perform the reduction of partial results and next in the same kernel. In this way, the overhead of maintaining activeness of vertices is trivial.
Since the frontier size is changing in different iterations, the size of the working set is also changing at runtime. At the beginning, there is only one active vertex, the source, in the frontier. And then the queue gets larger as more and more vertices are pushed into the frontier. After several iterations, the queue starts to shrink until it is empty. The working set size is proportional to the total degree of vertices in the frontier. When the frontier is small, the working set can possibly fit in cache. It is only when the frontier is large enough that cache blocking becomes beneficial.
As direction optimization (Beamer et al., 2012) divides an algorithm into different phases, we also use this hybrid method to enable cache blocking for traversal based algorithms. Take BC as an example. Algorithm 3 is split into three phases: push-pull-push. In the two push phases, the working set is usually smaller than the cache size, which means cache blocking may not be necessary. So we only apply TOCAB in the pull direction. Since the push kernel uses data-driven mapping to achieve work efficiency, this choice also avoids maintaining the frontier queue locally. For the pull kernel, applying TOCAB is the same as PR except updating next array. There are some iterations that run in push direction but have a working set larger than the cache capacity. Our experiment shows that these large push kernels are rare (usually one in each graph). Since the total graph size is confined by the memory size, the assumption is reasonable.
Finally we have a full solution for different kinds of graph algorithms. We integrate the optimizations into a unified framework, GraphCage. Programmers only write basic pull and push kernels to describe the operators for a specific algorithm, and GraphCage can apply cache optimizations to achieve high performance.
We evaluate performance of GraphCage on NVIDIA GTX 1080Ti with CUDA 9.1. The GPU has 3584 cores with boost clock of 1.6GHz. It has a 2.75 MB L2 cache and 11GB GDDR5X memory with 484 GB/s bandwidth. We use gcc and nvcc with the -O3 optimization option for compilation along with -arch=sm_61 when compiling for the GPU. We execute all the benchmarks 10 times and collect the average execution time to avoid system noise. Timing is only performed on the computation part of each program.
We select our datasets from the UF Sparse Matrix Collection (Davis and Hu, 2011), the SNAP dataset Collection (Leskovec, 2013), and the Koblenz Network Collection (Kunegis, 2013) which are all publicly available. Size, density, topology and application domains vary among these graphs. Note that graph layout has a tremendous impact on the locality of the vertex value accesses (Beamer et al., 2017). In another word, some graphs can benefit from locality optimizations, while some others may not, depending on their data layouts. The fact is that, for some graphs (e.g. social networks), their topologies make it difficult to find a good layout for high locality. Meanwhile, for some situations the time spent on transforming the graph into a locality-friendly layout is not warranted (Beamer et al., 2017). Therefore, our proposed method is useful in these cases. Our experiment mainly shows the performance improvement on graphs with poor locality, while we also show the performance effect on those graphs with good locality (Hollywood) to indicate that GraphCage only causes trivial slowdown.
|Graph||# V||# E||Description|
|LiveJ||4.8M||68M||14.2||LiveJournal social network|
|Wiki2007||3.6M||45M||12.6||Links in Wikipedia pages|
|Flickr||2.3M||33M||14.4||Flickr user connections|
|Wiki-link||12M||378M||31.1||Large Wikipedia links|
|Hollywood*||1.1M||112M||98.9||Movie actor network|
|Kron21*||2M||182M||86.8||Synthetic power-law graph|
|Orkut*||3M||212M||71.0||Orkut social network|
|Twitter*||21M||530M||24.9||Twitter user connections|
In this paper, we use two graph algorithms, i.e. PageRank (PR) and Betweenness Centrality (BC) as graph benchmarks. PR is non-traversal based and all the vertices in every iteration are active, while BC is a traversal based algorithm whose active vertex set (frontier queue) changes in each iteration during the execution. We also include sparse matrix vector multiplication (SpMV)(Bell and Garland, 2009) as another benchmark since most of graph algorithms can be mapped to generalized SpMV operations (Sundaram et al., 2015).
4.1. Performance Speedup
We first show PR performance improvement by comparing GraphCage with hand-optimized implementations. The baseline (Base) is a straightforward pull-style implementation with no optimizations. We then apply Virtual-Warp Centric (VWC) (Hong et al., 2011) method on the baseline, which is the second implementation. CB is the naive implementation of conventional cache blocking. We only implement cache blocking for PR. We also implement both PR-pull and PR-push in GraphCage. We run all the PR implementations until they converge with the same condition. Fig. 6 illustrates the performance speedups normalized to the baseline. On average, GraphCage significantly outperforms other implementations. VWC can consistently improve performance with an averaged speedup of 66% because of coalesced memory accesses. But it has little effect on temporal locality. CB works well for relatively small graphs with poor locality. For large graphs, such as Wiki-link and Twitter, it brings very limited benefit. For Hollywood, however, neither CB and GraphCage are able to get too much speedup, since this graph already has a layout with good locality. But GraphCage can substantially improve performance for large graphs, due to a better overhead control of our proposed TOCAB scheme. Since our cache blocking approach works well with load balancing, GraphCage consistently outperforms VWC (2 faster on average) except Hollywood which has a good layout that can benefit a lot from VWC, but not from locality optimizations. GC-pull and GC-push get similar average speedups, but have some difference for specific graphs. This is because GC-push has more overhead on atomic operations but its load balancing scheme is better than GC-pull.
We also evaluate the performance of SpMV and BC. Fig. 7 shows the speedups of SpMV implementations over the baseline. We can still observe consistent performance improvement for VWC and GraphCage. Compared to the baseline, VWC achieves more speedup (2.3 on average) for SpMV than PR because not only the column indices but also the matrix values are coalesced. The matrix values have no reuse opportunity, and could pollute the last level cache by replacing useful data. Therefore, GC-pull is less effective for SpMV compared to VWC. Since the effect of load balancing for SpMV is more significant than that for PR, we can observe better performance for GC-push than GC-pull, since fine-grained load balancing is applied to GC-push, but not GC-pull. On average, GC-push achieves 82% performance improvement over VWC.
Fig. 8 illustrates BC performance normalized to the baseline. Since we only applied TOCAB to the pull kernels, we have only one implementation for BC in GraphCage. The baseline is still a naive implementation, but we apply TWC in the second implementation since TWC works better than VWC for BC. We can still observe consistent improvement for TWC and GraphCage with averaged speedup of 2.8 and 4.1. A special issue here is that for large-diameter graphs, Wiki2007 and Wiki-link, GraphCage has limited improvement over TWC. This is because there are lots of iterations in these graphs have only one vertex in the frontier, For these iterations, GraphCage does not bring any benefit. Time spent on these iterations makes the improvement on other iterations less significant. However, we can still observe an average speedup of 46% for GraphCage compared to TWC.
4.2. Cache and Memory Efficiency
To relate the total performance improvement to the cache behavior, we conduct experiments on cache miss rates and memory efficiency. Fig. 9 shows the L2 cache miss rates for all the implementations in Fig. 6. It is quite clear that Base and TWC suffer tremendous cache misses with up to about 80% miss rate. In this case, the L2 cache is definitely poorly used and it could become a bottleneck in the memory hierarchy. Cache blocking techniques, however, can substantially reduce L2 cache miss rate. CB performs very well for Kron21 and Orkut which have plenty of data reuses. But for other graphs with poor locality, GraphCage can further reduce the miss rates to blow 20%. For Twitter, since it is large enough to have more than 80 subgraphs, CB suffer a huge overhead on repeated accesses and it fails to improve the cache behavior.
We further look into the DRAM access efficiency by calculating the number of DRAM accesses per edge. This ratio is one of the GAIL metrics to measure memory efficiency (Beamer et al., 2015a). As shown in Fig. 10, GraphCage significantly improves memory efficiency since accesses to the vertex values are kept in the L2 cache, reducing a huge amount of DRAM accesses. Although CB works well for Kron21 and Orkut, it actually increase the total number of DRAM accesses because of the repeated accesses to sums. This observation explains the reason why CB reduces L2 cache miss rate for Wiki-link but the performance improvement is very limited. The overhead of repeated accesses in CB is more significant for larger graphs, leading to even more DRAM accesses than the baseline. GraphCage, however, consistently achieves high memory efficiency.
4.3. Sensitivity to Subgraph Size
We vary the subgraph size to determine its impact on GraphCage’s performance. When the subgraphs are small enough to keep the vertex values reside in the cache, the memory efficiency is high, since most of the vertex value accesses hit in the cache instead of going to the DRAM. However, if the subgraphs are too small, there will be numerous subgraphs, which brings more overhead on merging the partial results of the subgraphs. Thus the performance will not be good. On the other hand, when the subgraph size becomes too large, the vertex values data no longer fit in the cache, and the memory efficiency drops increasingly. Therefore, the subgraph size is a trade-off between locality benefits and the overhead of partitioning. For our platform, we select a subgraph size of 256 vertices, since it makes a good trade-off and achieves best performance.
4.4. Comparing with Other Frameworks
We compare GraphCage with the state-of-the-art GPU graph processing framework Gunrock (Wang et al., 2016), and another graph partitioning framework on GPUs, CuSha (Khorasani et al., 2014). The averaged execution time of one PageRank iteration is listed in Table 3. We use the runtime of one iteration instead of runtime of convergence because CuSha use a different convergence condition. We can observe that GraphCage substantially outperforms Gunrock for all the graphs with poor locality, especially those graphs with large average degrees which have great potential to enjoy locality optimizations. On average, GC-pull and GC-push achieve 4.0 and 3.6 speedup over Gunrock for our tested graphs, respectively. Since there is no cache blocking technique applied in Gunrock to improve cache performance, this dramatic improvement is expected. For CuSha, however, it partitions graphs into Shards to fit into the shared memory, which also improves performance for LiveJ, Kron21 and Orkut. It is extremely inefficient for the two wiki graphs which have large diameters. This issue could be caused by the difference of convergence condition. However, GraphCage is still faster or as fast as CuSha for other graphs, because of the overhead of relatively small partitions in CuSha. Table 4 list the number of partitions in GraphCage and CuSha. Last but not least, CuSha fails to run Twitter on GTX 1080Ti because it requires more global memory than the GPU provides. This is also very important as global memory in GPUs is scarce resource and GraphCage can provide more stable performance with less global memory requirement than CuSha. We also compare GraphCage with other available implementations. For example, we compare SpMV with cuSPARSE (NVIDIA, 2016a) and nvGRAPH (NVIDIA, 2016b). GraphCage also significantly outperforms these commercial libraries.
|46.69||67.76||190.48||out of memory|
5. Related Work
There are plenty of previous work on dynamic cache blocking targeting push-style graph processing on CPUs. Beamer et al. (Beamer et al., 2015b) proposed Propagation Blocking (PB) (Beamer et al., 2017) for PageRank. They split the computation into two phases, binning and accumulation. In the binning phase, intermediate data is distributed into different buffers (bins) whose sizes can fit in the cache. And then buffers are processed sequentially in the accumulation phase to merge the results. Similar approach is used to improve the performance of SpMV kernel in graph analytics applications (Buono et al., 2016). milk is a language extension on OpenMP (Kiriansky et al., 2016). It automatically collects indirect memory accesses at runtime in order to improve locality. Comparing to these dynamic schemes on CPUs, our design use static cache blocking to reduce runtime overhead as much as possible.
Zhang et al. propose CSR segmenting (Zhang et al., 2017), an efficient 1D static cache blocking technique for multicore CPUs. CSR segmenting partitions a graph into segments, each of which can fit in the cache. They also propose a merge phase to accumulate the partial results. Gluon (Dathathri et al., 2018) is a graph processing system targeting heterogeneous distributed-memory platforms. Gluon statically partitions the graph into subgraphs to fit into distributed memories instead of caches. Their partitioning strategies for share-memory CPUs and distributed memory systems inspired our work. GraphCage is also a static cache blocking scheme, but we optimize it for the GPU architecture and integrate it with previously proposed optimization techniques for GPU graph processing.
GraphChi (Kyrola et al., 2012) is a edge centric graph processing system which divides edges into shards to improve data locality on CPUs. CuSha (Khorasani et al., 2014) extends this concept to GPU graph processing. They optimize it for the GPU execution model and use a COO-like graph format called Concatenated Windows (CW) to store edges consecutively. This is particularly beneficial for GPU processing as memory accesses are fully coalesced. However, CW representation requires roughly 2.5x the size of CSR, which is not acceptable for GPUs whose global memory capacity is already very limited. By contrast, GraphCage requires much less memory space as we directly extends the data representation from CSR. Another issue is that Shards are constructed to fit into shared memory instead of LLC, which limits the subgraph size and brings more overhead.
There are also cache blocking schemes designed for other architectures. Graphphi (Peng et al., 2018) designed cache blocking for Intel Xeon Phi processors. They proposed a hierarchical partitioning scheme to divide a graph into groups, stripes and tiles respectively. They use COO format instead of CSR to store the graph, and optimize the processing for Xeon Phi’s MIMD-SIMD execution model. Graphicionado (Ham et al., 2016) is a customized hardware accelerator for graph processing. It provides a large eDRAM on-chip as a scratchpad memory to the programmers to explicitly arrange data accesses. The authors propose a graph partitioning scheme to keep data in the scratchpad, substantially reduce the memory access latency. However their scheme only discusses push-style graph processing, and is specifically optimized for their accelerator architecture. In comparison, GraphCage is optimized for GPUs and handles applications in both push and pull directions.
Beamer et al. (Beamer et al., 2015b) analyzed data locality of graph algorithms on CPUs. They found that there are actually plenty of data reuses in graph algorithms, but existing hardware is not able to capture them, which leads to poor cache performance. Xu et al. (Xu et al., 2014) evaluated cache performance of graph processing on GPUs. They found that GPUs also suffer this problem. Their observation motivates our work. Since hardware is unaware of the memory access pattern of graph algorithms, we started to look into the software layer and rearrange the memory access order.
Data locality is a key performance issue for graph processing. In this paper, we point out that there is a chance to improve locality as well as performance of GPU graph processing by leveraging the last level cache. We first propose a throughput oriented cache blocking (TOCAB) approach for the GPU architecture which improves memory access efficiency compared to naively applying conventional cache blocking scheme. Then we coordinate our cache blocking scheme with state-of-the-art load balancing scheme with awareness of the sparsity of subgraphs. Finally we apply our scheme to traversal based graph algorithm by considering both the benefit and overhead of cache blocking. These optimization techniques are integrated into our graph processing framework, i.e. GraphCage, to support various graph applications. Experimental results show that GraphCage can significantly improve performance over previous best-performing implementations and state-of-the-art graph processing frameworks such as Gunrock and CuSha, with less memory space requirement than CuSha. With the continuous increasing of GPU last level cache size, our proposed approach will be more effective and important for graph processing on GPUs in the future.
- Anderson et al. (2016) M. J. Anderson, N. Sundaram, N. Satish, M. M. A. Patwary, T. L. Willke, and P. Dubey. 2016. GraphPad: Optimized Graph Primitives for Parallel and Distributed Platforms. In 2016 IEEE International Parallel and Distributed Processing Symposium (IPDPS), 313–322.
- Beamer et al. (2012) Scott Beamer, Krste Asanović, and David Patterson. 2012. Direction-optimizing Breadth-first Search. In Proceedings of the International Conference on High Performance Computing, Networking, Storage and Analysis (SC), Article 12, 10 pages.
- Beamer et al. (2015a) Scott Beamer, Krste Asanović, and David Patterson. 2015a. GAIL: The Graph Algorithm Iron Law. In Proceedings of the 5th Workshop on Irregular Applications: Architectures and Algorithms, Article 13, 4 pages.
- Beamer et al. (2015b) S. Beamer, K. Asanovic, and D. Patterson. 2015b. Locality Exists in Graph Processing: Workload Characterization on an Ivy Bridge Server. In Proceedings of the IEEE International Symposium on Workload Characterization (IISWC), 56–65.
- Beamer et al. (2017) S. Beamer, K. Asanović, and D. Patterson. 2017. Reducing Pagerank Communication via Propagation Blocking. In 2017 IEEE International Parallel and Distributed Processing Symposium (IPDPS), 820–831.
- Bell and Garland (2009) Nathan Bell and Michael Garland. 2009. Implementing Sparse Matrix-vector Multiplication on Throughput-oriented Processors. In Proceedings of the Conference on High Performance Computing Networking, Storage and Analysis, Article 18, 11 pages.
- Besta et al. (2017) Maciej Besta, MichałPodstawski, Linus Groner, Edgar Solomonik, and Torsten Hoefler. 2017. To Push or To Pull: On Reducing Communication and Synchronization in Graph Computations. In Proceedings of the 26th International Symposium on High-Performance Parallel and Distributed Computing, 93–104.
- Buluç et al. (2009) Aydin Buluç, Jeremy T. Fineman, Matteo Frigo, John R. Gilbert, and Charles E. Leiserson. 2009. Parallel Sparse Matrix-vector and Matrix-transpose-vector Multiplication Using Compressed Sparse Blocks. In Proceedings of the Twenty-first Annual Symposium on Parallelism in Algorithms and Architectures, 233–244.
- Buono et al. (2016) Daniele Buono, Fabrizio Petrini, Fabio Checconi, Xing Liu, Xinyu Que, Chris Long, and Tai-Ching Tuan. 2016. Optimizing Sparse Matrix-Vector Multiplication for Large-Scale Data Analytics. In Proceedings of the 2016 International Conference on Supercomputing, Article 37, 12 pages.
- Chen et al. (2014) Xuhao Chen, Li-Wen Chang, Christopher I. Rodrigues, Jie Lv, Zhiying Wang, and Wen-Mei Hwu. 2014. Adaptive Cache Management for Energy-Efficient GPU Computing. In Proceedings of the 47th Annual IEEE/ACM International Symposium on Microarchitecture (MICRO), 343–355.
- Dathathri et al. (2018) Roshan Dathathri, Gurbinder Gill, Loc Hoang, Hoang-Vu Dang, Alex Brooks, Nikoli Dryden, Marc Snir, and Keshav Pingali. 2018. Gluon: A Communication-optimizing Substrate for Distributed Heterogeneous Graph Analytics. In Proceedings of the 39th ACM SIGPLAN Conference on Programming Language Design and Implementation, 752–768.
- Davidson et al. (2014) A. Davidson, S. Baxter, M. Garland, and J. D. Owens. 2014. Work-Efficient Parallel GPU Methods for Single-Source Shortest Paths. In Proceedings of the IEEE 28th International Parallel and Distributed Processing Symposium (IPDPS), 349–359.
- Davis and Hu (2011) Timothy A Davis and Yifan Hu. 2011. The University of Florida sparse matrix collection. ACM Transactions on Mathematical Software (TOMS) 38, 1 (2011), 1.
- Fu et al. (2014) Zhisong Fu, Michael Personick, and Bryan Thompson. 2014. MapGraph: A High Level API for Fast Development of High Performance Graph Analytics on GPUs. In Proceedings of Workshop on GRAph Data Management Experiences and Systems, Article 2, 6 pages.
- Grossman et al. (2018) Samuel Grossman, Heiner Litz, and Christos Kozyrakis. 2018. Making Pull-based Graph Processing Performant. In Proceedings of the 23rd ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming, 246–260.
- Ham et al. (2016) T. J. Ham, L. Wu, N. Sundaram, N. Satish, and M. Martonosi. 2016. Graphicionado: A high-performance and energy-efficient accelerator for graph analytics. In 2016 49th Annual IEEE/ACM International Symposium on Microarchitecture (MICRO), 1–13.
- Hong et al. (2011) Sungpack Hong, Sang Kyun Kim, Tayo Oguntebi, and Kunle Olukotun. 2011. Accelerating CUDA Graph Algorithms at Maximum Warp. In Proceedings of the 16th ACM Symposium on Principles and Practice of Parallel Programming (PPoPP), 267–276.
- Jia et al. (2012) Wenhao Jia, Kelly A. Shaw, and Margaret Martonosi. 2012. Characterizing and Improving the Use of Demand-fetched Caches in GPUs. In Proceedings of the 26th ACM International Conference on Supercomputing, 15–24.
- Khorasani et al. (2014) Farzad Khorasani, Keval Vora, Rajiv Gupta, and Laxmi N. Bhuyan. 2014. CuSha: Vertex-centric Graph Processing on GPUs. In Proceedings of the 23rd International Symposium on High-performance Parallel and Distributed Computing (HPDC), 239–252.
- Kiriansky et al. (2016) Vladimir Kiriansky, Yunming Zhang, and Saman Amarasinghe. 2016. Optimizing Indirect Memory References with Milk. In Proceedings of the 2016 International Conference on Parallel Architectures and Compilation, 299–312.
- Kunegis (2013) Jérôme Kunegis. 2013. Konect: the koblenz network collection. In Proceedings of the 22nd International Conference on World Wide Web, 1343–1350.
- Kyrola et al. (2012) Aapo Kyrola, Guy Blelloch, and Carlos Guestrin. 2012. GraphChi: Large-Scale Graph Computation on Just a PC. In Proceedings of the 10th USENIX Symposium on Operating Systems Design and Implementation (OSDI 12), 31–46.
- Leskovec (2013) J. Leskovec. 2013. SNAP: Stanford Network Analysis Platform. (2013). http://snap.stanford.edu/data/index.html
- Liu and Huang (2015) Hang Liu and H. Howie Huang. 2015. Enterprise: Breadth-first Graph Traversal on GPUs. In Proceedings of the International Conference for High Performance Computing, Networking, Storage and Analysis (SC), Article 68, 12 pages.
- Low et al. (2012) Yucheng Low, Danny Bickson, Joseph Gonzalez, Carlos Guestrin, Aapo Kyrola, and Joseph M. Hellerstein. 2012. Distributed GraphLab: A Framework for Machine Learning and Data Mining in the Cloud. Proceedings of the VLDB Endowment 5, 8 (April 2012), 716–727.
- Low et al. (2010) Yucheng Low, Joseph Gonzalez, Aapo Kyrola, Danny Bickson, Carlos Guestrin, and Joseph M. Hellerstein. 2010. Graphlab: A new parallel framework for machine learning. In Proceedings of the UAI, 340–349.
- Luo et al. (2010) Lijuan Luo, Martin Wong, and Wen-mei Hwu. 2010. An Effective GPU Implementation of Breadth-first Search. In Proceedings of the 47th Design Automation Conference (DAC), 52–55.
- Malewicz et al. (2010) Grzegorz Malewicz, Matthew H. Austern, Aart J.C Bik, James C. Dehnert, Ilan Horn, Naty Leiser, and Grzegorz Czajkowski. 2010. Pregel: A System for Large-scale Graph Processing. In Proceedings of the ACM SIGMOD International Conference on Management of Data, 135–146.
- McLaughlin and Bader (2014) Adam McLaughlin and David A. Bader. 2014. Scalable and High Performance Betweenness Centrality on the GPU. In Proceedings of the International Conference for High Performance Computing, Networking, Storage and Analysis (SC), 572–583.
- Merrill et al. (2012) Duane Merrill, Michael Garland, and Andrew Grimshaw. 2012. Scalable GPU Graph Traversal. In Proceedings of the 17th ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming (PPoPP), 117–128.
- Nasre et al. (2013) R. Nasre, M. Burtscher, and K. Pingali. 2013. Data-Driven Versus Topology-driven Irregular Computations on GPUs. In Proceedings of the 27th IEEE International Parallel Distributed Processing Symposium (IPDPS), 463–474.
- Nguyen et al. (2013) Donald Nguyen, Andrew Lenharth, and Keshav Pingali. 2013. A Lightweight Infrastructure for Graph Analytics. In Proceedings of the 24th ACM Symposium on Operating Systems Principles (SOSP), 456–471.
- Nishtala et al. (2007) Rajesh Nishtala, Richard W. Vuduc, James W. Demmel, and Katherine A. Yelick. 2007. When cache blocking of sparse matrix vector multiply works and why. Applicable Algebra in Engineering, Communication and Computing 18, 3 (01 May 2007), 297–311.
- NVIDIA (2016a) NVIDIA. 2016a. cuSPARSE Library. http://docs.nvidia.com/cuda/cusparse/. (2016).
- NVIDIA (2016b) NVIDIA. 2016b. nvGRAPH Library. (2016). http://docs.nvidia.com/cuda/nvgraph/index.html
- Page et al. (1998) Larry Page, Sergey Brin, R. Motwani, and T. Winograd. 1998. The PageRank Citation Ranking: Bringing Order to the Web. (1998).
- Peng et al. (2018) Zhen Peng, Alexander Powell, Bo Wu, Tekin Bicer, and Bin Ren. 2018. Graphphi: Efficient Parallel Graph Processing on Emerging Throughput-oriented Architectures. In Proceedings of the 27th International Conference on Parallel Architectures and Compilation Techniques, Article 9, 14 pages.
- Pingali et al. (2011) Keshav Pingali, Donald Nguyen, Milind Kulkarni, Martin Burtscher, M. Amber Hassaan, Rashid Kaleem, Tsung-Hsien Lee, Andrew Lenharth, Roman Manevich, Mario Méndez-Lojo, Dimitrios Prountzos, and Xin Sui. 2011. The Tao of Parallelism in Algorithms. In Proceedings of the 32Nd ACM SIGPLAN Conference on Programming Language Design and Implementation, 12–25.
- Sengupta et al. (2015) Dipanjan Sengupta, Shuaiwen Leon Song, Kapil Agarwal, and Karsten Schwan. 2015. GraphReduce: Processing Large-scale Graphs on Accelerator-based Systems. In Proceedings of the International Conference for High Performance Computing, Networking, Storage and Analysis, Article 28, 12 pages.
- Shun and Blelloch (2013) Julian Shun and Guy E. Blelloch. 2013. Ligra: A Lightweight Graph Processing Framework for Shared Memory. In Proceedings of the 18th ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming (PPoPP), 135–146.
- Sun et al. (2017) Jiawen Sun, Hans Vandierendonck, and Dimitrios S. Nikolopoulos. 2017. GraphGrind: Addressing Load Imbalance of Graph Partitioning. In Proceedings of the International Conference on Supercomputing, Article 16, 10 pages.
- Sundaram et al. (2015) Narayanan Sundaram, Nadathur Satish, Md Mostofa Ali Patwary, Subramanya R. Dulloor, Michael J. Anderson, Satya Gautam Vadlamudi, Dipankar Das, and Pradeep Dubey. 2015. GraphMat: High Performance Graph Analytics Made Productive. Proc. VLDB Endow. 8, 11 (July 2015), 1214–1225.
- Sutton et al. (2018) M. Sutton, T. Ben-Nun, and A. Barak. 2018. Optimizing Parallel Graph Connectivity Computation via Subgraph Sampling. In 2018 IEEE International Parallel and Distributed Processing Symposium (IPDPS), 12–21.
- Wang et al. (2016) Yangzihao Wang, Andrew Davidson, Yuechao Pan, Yuduo Wu, Andy Riffel, and John D. Owens. 2016. Gunrock: A High-Performance Graph Processing Library on the GPU. In Proceedings of the 21st ACM SIGPLAN Symposium on Principles and Practice of Parallel Programming.
- Xu et al. (2014) Q. Xu, H. Jeon, and M. Annavaram. 2014. Graph processing on GPUs: Where are the bottlenecks?. In Proceedings of the IEEE International Symposium on Workload Characterization (IISWC), 140–149.
- Zhang et al. (2017) Y. Zhang, V. Kiriansky, C. Mendis, S. Amarasinghe, and M. Zaharia. 2017. Making caches work for graph analytics. In 2017 IEEE International Conference on Big Data (Big Data), 293–302.
- Zhong and He (2014) J. Zhong and B. He. 2014. Medusa: Simplified Graph Processing on GPUs. IEEE Transactions on Parallel and Distributed Systems 25, 6 (June 2014), 1543–1552.
- Zhu et al. (2016) Xiaowei Zhu, Wenguang Chen, Weimin Zheng, and Xiaosong Ma. 2016. Gemini: A Computation-Centric Distributed Graph Processing System. In 12th USENIX Symposium on Operating Systems Design and Implementation (OSDI 16), 301–316.