OpenCLSort.cpp 9.66 KB
Newer Older
1
2
3
4
5
6
7
8
/* -------------------------------------------------------------------------- *
 *                                   OpenMM                                   *
 * -------------------------------------------------------------------------- *
 * This is part of the OpenMM molecular simulation toolkit originating from   *
 * Simbios, the NIH National Center for Physics-Based Simulation of           *
 * Biological Structures at Stanford, funded under the NIH Roadmap for        *
 * Medical Research, grant U54 GM072970. See https://simtk.org.               *
 *                                                                            *
peastman's avatar
peastman committed
9
 * Portions copyright (c) 2010-2018 Stanford University and the Authors.      *
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 * Authors: Peter Eastman                                                     *
 * Contributors:                                                              *
 *                                                                            *
 * This program is free software: you can redistribute it and/or modify       *
 * it under the terms of the GNU Lesser General Public License as published   *
 * by the Free Software Foundation, either version 3 of the License, or       *
 * (at your option) any later version.                                        *
 *                                                                            *
 * 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              *
 * GNU Lesser General Public License for more details.                        *
 *                                                                            *
 * You should have received a copy of the GNU Lesser General Public License   *
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.      *
 * -------------------------------------------------------------------------- */

peastman's avatar
peastman committed
27
28
29
30
#ifdef _MSC_VER
    // Prevent Windows from defining macros that interfere with other code.
    #define NOMINMAX
#endif
31
32
#include "OpenCLSort.h"
#include "OpenCLKernelSources.h"
peastman's avatar
peastman committed
33
#include <algorithm>
34
35
36
37
38
#include <map>

using namespace OpenMM;
using namespace std;

peastman's avatar
peastman committed
39
OpenCLSort::OpenCLSort(OpenCLContext& context, SortTrait* trait, unsigned int length) : context(context), trait(trait), dataLength(length) {
40
41
42
43
44
45
46
47
48
49
50
    // Create kernels.

    std::map<std::string, std::string> replacements;
    replacements["DATA_TYPE"] = trait->getDataType();
    replacements["KEY_TYPE"] =  trait->getKeyType();
    replacements["SORT_KEY"] = trait->getSortKey();
    replacements["MIN_KEY"] = trait->getMinKey();
    replacements["MAX_KEY"] = trait->getMaxKey();
    replacements["MAX_VALUE"] = trait->getMaxValue();
    cl::Program program = context.createProgram(context.replaceStrings(OpenCLKernelSources::sort, replacements));
    shortListKernel = cl::Kernel(program, "sortShortList");
peastman's avatar
peastman committed
51
    shortList2Kernel = cl::Kernel(program, "sortShortList2");
52
53
54
55
56
57
58
59
60
61
    computeRangeKernel = cl::Kernel(program, "computeRange");
    assignElementsKernel = cl::Kernel(program, "assignElementsToBuckets");
    computeBucketPositionsKernel = cl::Kernel(program, "computeBucketPositions");
    copyToBucketsKernel = cl::Kernel(program, "copyDataToBuckets");
    sortBucketsKernel = cl::Kernel(program, "sortBuckets");

    // Work out the work group sizes for various kernels.

    unsigned int maxGroupSize = std::min(256, (int) context.getDevice().getInfo<CL_DEVICE_MAX_WORK_GROUP_SIZE>());
    int maxSharedMem = context.getDevice().getInfo<CL_DEVICE_LOCAL_MEM_SIZE>();
62
63
    unsigned int maxRangeSize = std::min(maxGroupSize, (unsigned int) computeRangeKernel.getWorkGroupInfo<CL_KERNEL_WORK_GROUP_SIZE>(context.getDevice()));
    unsigned int maxPositionsSize = std::min(maxGroupSize, (unsigned int) computeBucketPositionsKernel.getWorkGroupInfo<CL_KERNEL_WORK_GROUP_SIZE>(context.getDevice()));
peastman's avatar
peastman committed
64
65
66
    int maxLocalBuffer = (maxSharedMem/trait->getDataSize())/2;
    unsigned int maxShortList = min(8192, max(maxLocalBuffer, (int) OpenCLContext::ThreadBlockSize*context.getNumThreadBlocks()));
    // On Qualcomm's OpenCL, it's essential to check against CL_KERNEL_WORK_GROUP_SIZE.  Otherwise you get a crash.
67
68
69
    // But AMD's OpenCL returns an inappropriately small value for it that is much shorter than the actual
    // maximum, so including the check hurts performance.  For the moment I'm going to just comment it out.
    // If we officially support Qualcomm in the future, we'll need to do something better.
peastman's avatar
peastman committed
70
71
    //maxShortList = min(maxShortList, shortListKernel.getWorkGroupInfo<CL_KERNEL_WORK_GROUP_SIZE>(context.getDevice()));
    isShortList = (length <= maxShortList);
72
    for (rangeKernelSize = 1; rangeKernelSize*2 <= maxRangeSize; rangeKernelSize *= 2)
73
        ;
74
    positionsKernelSize = std::min(rangeKernelSize, maxPositionsSize);
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
    sortKernelSize = (isShortList ? rangeKernelSize : rangeKernelSize/2);
    if (rangeKernelSize > length)
        rangeKernelSize = length;
    if (sortKernelSize > maxLocalBuffer)
        sortKernelSize = maxLocalBuffer;
    unsigned int targetBucketSize = sortKernelSize/2;
    unsigned int numBuckets = length/targetBucketSize;
    if (numBuckets < 1)
        numBuckets = 1;
    if (positionsKernelSize > numBuckets)
        positionsKernelSize = numBuckets;

    // Create workspace arrays.

    if (!isShortList) {
peastman's avatar
peastman committed
90
91
92
93
        dataRange.initialize(context, 2, trait->getKeySize(), "sortDataRange");
        bucketOffset.initialize<cl_uint>(context, numBuckets, "bucketOffset");
        bucketOfElement.initialize<cl_uint>(context, length, "bucketOfElement");
        offsetInBucket.initialize<cl_uint>(context, length, "offsetInBucket");
94
    }
peastman's avatar
peastman committed
95
    buckets.initialize(context, length, trait->getDataSize(), "buckets");
96
97
98
99
100
101
102
103
104
105
106
107
}

OpenCLSort::~OpenCLSort() {
    delete trait;
}

void OpenCLSort::sort(OpenCLArray& data) {
    if (data.getSize() != dataLength || data.getElementSize() != trait->getDataSize())
        throw OpenMMException("OpenCLSort called with different data size");
    if (data.getSize() == 0)
        return;
    if (isShortList) {
peastman's avatar
peastman committed
108
        // We can use a simpler sort kernel that does the entire operation in one kernel.
109
        
peastman's avatar
peastman committed
110
111
112
113
114
115
116
117
118
119
120
121
122
        if (dataLength <= OpenCLContext::ThreadBlockSize*context.getNumThreadBlocks()) {
            shortList2Kernel.setArg<cl::Buffer>(0, data.getDeviceBuffer());
            shortList2Kernel.setArg<cl::Buffer>(1, buckets.getDeviceBuffer());
            shortList2Kernel.setArg<cl_int>(2, dataLength);
            context.executeKernel(shortList2Kernel, dataLength);
            buckets.copyTo(data);
        }
        else {
            shortListKernel.setArg<cl::Buffer>(0, data.getDeviceBuffer());
            shortListKernel.setArg<cl_uint>(1, dataLength);
            shortListKernel.setArg(2, dataLength*trait->getDataSize(), NULL);
            context.executeKernel(shortListKernel, sortKernelSize, sortKernelSize);
        }
123
124
125
126
    }
    else {
        // Compute the range of data values.

peastman's avatar
peastman committed
127
        unsigned int numBuckets = bucketOffset.getSize();
128
129
        computeRangeKernel.setArg<cl::Buffer>(0, data.getDeviceBuffer());
        computeRangeKernel.setArg<cl_uint>(1, data.getSize());
peastman's avatar
peastman committed
130
        computeRangeKernel.setArg<cl::Buffer>(2, dataRange.getDeviceBuffer());
131
        computeRangeKernel.setArg(3, rangeKernelSize*trait->getKeySize(), NULL);
Peter Eastman's avatar
Peter Eastman committed
132
133
        computeRangeKernel.setArg(4, rangeKernelSize*trait->getKeySize(), NULL);
        computeRangeKernel.setArg<cl_int>(5, numBuckets);
peastman's avatar
peastman committed
134
        computeRangeKernel.setArg<cl::Buffer>(6, bucketOffset.getDeviceBuffer());
135
136
137
138
139
140
141
        context.executeKernel(computeRangeKernel, rangeKernelSize, rangeKernelSize);

        // Assign array elements to buckets.

        assignElementsKernel.setArg<cl::Buffer>(0, data.getDeviceBuffer());
        assignElementsKernel.setArg<cl_int>(1, data.getSize());
        assignElementsKernel.setArg<cl_int>(2, numBuckets);
peastman's avatar
peastman committed
142
143
144
145
        assignElementsKernel.setArg<cl::Buffer>(3, dataRange.getDeviceBuffer());
        assignElementsKernel.setArg<cl::Buffer>(4, bucketOffset.getDeviceBuffer());
        assignElementsKernel.setArg<cl::Buffer>(5, bucketOfElement.getDeviceBuffer());
        assignElementsKernel.setArg<cl::Buffer>(6, offsetInBucket.getDeviceBuffer());
146
147
148
149
150
        context.executeKernel(assignElementsKernel, data.getSize());

        // Compute the position of each bucket.

        computeBucketPositionsKernel.setArg<cl_int>(0, numBuckets);
peastman's avatar
peastman committed
151
        computeBucketPositionsKernel.setArg<cl::Buffer>(1, bucketOffset.getDeviceBuffer());
152
153
154
155
156
157
        computeBucketPositionsKernel.setArg(2, positionsKernelSize*sizeof(cl_int), NULL);
        context.executeKernel(computeBucketPositionsKernel, positionsKernelSize, positionsKernelSize);

        // Copy the data into the buckets.

        copyToBucketsKernel.setArg<cl::Buffer>(0, data.getDeviceBuffer());
peastman's avatar
peastman committed
158
        copyToBucketsKernel.setArg<cl::Buffer>(1, buckets.getDeviceBuffer());
159
        copyToBucketsKernel.setArg<cl_int>(2, data.getSize());
peastman's avatar
peastman committed
160
161
162
        copyToBucketsKernel.setArg<cl::Buffer>(3, bucketOffset.getDeviceBuffer());
        copyToBucketsKernel.setArg<cl::Buffer>(4, bucketOfElement.getDeviceBuffer());
        copyToBucketsKernel.setArg<cl::Buffer>(5, offsetInBucket.getDeviceBuffer());
163
164
165
166
167
        context.executeKernel(copyToBucketsKernel, data.getSize());

        // Sort each bucket.

        sortBucketsKernel.setArg<cl::Buffer>(0, data.getDeviceBuffer());
peastman's avatar
peastman committed
168
        sortBucketsKernel.setArg<cl::Buffer>(1, buckets.getDeviceBuffer());
169
        sortBucketsKernel.setArg<cl_int>(2, numBuckets);
peastman's avatar
peastman committed
170
        sortBucketsKernel.setArg<cl::Buffer>(3, bucketOffset.getDeviceBuffer());
171
172
173
174
        sortBucketsKernel.setArg(4, sortKernelSize*trait->getDataSize(), NULL);
        context.executeKernel(sortBucketsKernel, ((data.getSize()+sortKernelSize-1)/sortKernelSize)*sortKernelSize, sortKernelSize);
    }
}