├── .gitignore ├── README.markdown ├── AllocateMirrored.h ├── main.m ├── MAMirroredQueue.h ├── LICENSE ├── AllocateMirrored.c └── MAMirroredQueue.m /.gitignore: -------------------------------------------------------------------------------- 1 | a.out 2 | *.dSYM 3 | -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | # MAMirroredQueue 2 | 3 | MAMirroredQueue is a ring buffer implementation that uses virtual memory tricks to achieve fast operation, avoid copying data, and thread safety without using locks. -------------------------------------------------------------------------------- /AllocateMirrored.h: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | void *allocate_mirrored(size_t howmuch, unsigned howmany); 5 | void free_mirrored(void *ptr, size_t howmuch, unsigned howmany); 6 | size_t get_page_size(void); 7 | 8 | void test_allocate_mirrored(void); 9 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | // clang -framework Foundation -W -Wall -Wno-unused-parameter -fobjc-arc main.m AllocateMirrored.c MAMirroredQueue.m 2 | 3 | #import 4 | 5 | #import "AllocateMirrored.h" 6 | #import "MAMirroredQueue.h" 7 | 8 | 9 | int main(int argc, char **argv) 10 | { 11 | @autoreleasepool 12 | { 13 | test_allocate_mirrored(); 14 | [MAMirroredQueue runTests]; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /MAMirroredQueue.h: -------------------------------------------------------------------------------- 1 | 2 | #import 3 | 4 | 5 | @interface MAMirroredQueue : NSObject 6 | 7 | - (size_t)availableBytes; 8 | - (void *)readPointer; 9 | - (void)advanceReadPointer: (size_t)howmuch; 10 | 11 | - (BOOL)ensureWriteSpace: (size_t)howmuch; 12 | - (void *)writePointer; 13 | - (void)advanceWritePointer: (size_t)howmuch; 14 | 15 | - (void)lockAllocation; 16 | - (void)unlockAllocation; 17 | 18 | // UNIX-like wrappers 19 | 20 | - (size_t)read: (void *)buf count: (size_t)howmuch; 21 | - (size_t)write: (const void *)buf count: (size_t)howmuch; 22 | 23 | @end 24 | 25 | @interface MAMirroredQueue (Testing) 26 | 27 | + (void)runTests; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MAMirroredQueue is distributed under a BSD license, as listed below. 2 | 3 | 4 | Copyright (c) 2010, Michael Ash 5 | All rights reserved. 6 | 7 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 8 | 9 | Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | 11 | Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 12 | 13 | Neither the name of Michael Ash nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /AllocateMirrored.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "AllocateMirrored.h" 9 | 10 | 11 | void *allocate_mirrored(size_t howmuch, unsigned howmany) 12 | { 13 | // make sure it's positive and an exact multiple of page size 14 | if(howmuch <= 0 || howmuch != trunc_page(howmuch)) 15 | { 16 | errno = EINVAL; 17 | return NULL; 18 | } 19 | 20 | char *mem = NULL; 21 | while(mem == NULL) 22 | { 23 | #define CHECK_ERR(expr, todealloc) do { \ 24 | kern_return_t __check_err = (expr); \ 25 | if(__check_err != KERN_SUCCESS) \ 26 | { \ 27 | if(todealloc > 0) \ 28 | vm_deallocate(mach_task_self(), (vm_address_t)mem, (todealloc)); \ 29 | errno = ENOMEM; \ 30 | return NULL; \ 31 | } \ 32 | } while(0) 33 | 34 | CHECK_ERR(vm_allocate(mach_task_self(), (vm_address_t *)&mem, howmuch * howmany, VM_FLAGS_ANYWHERE), 0); 35 | 36 | char *target = mem + howmuch; 37 | CHECK_ERR(vm_deallocate(mach_task_self(), (vm_address_t)target, howmuch * (howmany - 1)), howmuch * howmany); 38 | 39 | for(unsigned i = 1; i < howmany && mem != NULL; i++) 40 | { 41 | vm_prot_t curProtection, maxProtection; 42 | kern_return_t err = vm_remap(mach_task_self(), 43 | (vm_address_t *)&target, 44 | howmuch, 45 | 0, // mask 46 | 0, // anywhere 47 | mach_task_self(), 48 | (vm_address_t)mem, 49 | 0, // copy 50 | &curProtection, 51 | &maxProtection, 52 | VM_INHERIT_COPY); 53 | target += howmuch; 54 | 55 | if(err == KERN_NO_SPACE) 56 | { 57 | CHECK_ERR(vm_deallocate(mach_task_self(), (vm_address_t)mem, howmuch * i), 0); 58 | mem = NULL; 59 | } 60 | else 61 | { 62 | CHECK_ERR(err, howmuch * i); 63 | } 64 | } 65 | } 66 | return mem; 67 | } 68 | 69 | void free_mirrored(void *ptr, size_t howmuch, unsigned howmany) 70 | { 71 | vm_deallocate(mach_task_self(), (vm_address_t)ptr, howmuch * howmany); 72 | } 73 | 74 | size_t get_page_size(void) 75 | { 76 | return vm_page_size; 77 | } 78 | 79 | // test code here 80 | 81 | static void test_size(unsigned howmany, size_t howmuch) 82 | { 83 | char *buf = allocate_mirrored(howmuch, howmany); 84 | 85 | unsigned short seed[3] = { 0 }; 86 | for(unsigned j = 0; j < howmany; j++) 87 | { 88 | for(size_t i = 0; i < howmuch; i++) 89 | buf[i] = nrand48(seed); 90 | if(memcmp(buf, buf + howmuch * j, howmuch) != 0) 91 | fprintf(stderr, "FAIL: writing to first half didn't update second half with size %lu\n", (long)howmuch); 92 | 93 | for(size_t i = 0; i < howmuch; i++) 94 | buf[howmuch * j + i] = nrand48(seed); 95 | if(memcmp(buf, buf + howmuch * j, howmuch) != 0) 96 | fprintf(stderr, "FAIL: writing to second half didn't update first half with size %lu\n", (long)howmuch); 97 | } 98 | 99 | free_mirrored(buf, howmuch, howmany); 100 | } 101 | 102 | void test_allocate_mirrored(void) 103 | { 104 | for(unsigned i = 2; i < 10; i++) 105 | { 106 | test_size(i, get_page_size()); 107 | test_size(i, get_page_size() * 2); 108 | test_size(i, get_page_size() * 10); 109 | test_size(i, get_page_size() * 100); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /MAMirroredQueue.m: -------------------------------------------------------------------------------- 1 | 2 | #import "MAMirroredQueue.h" 3 | 4 | #include 5 | 6 | #import "AllocateMirrored.h" 7 | 8 | 9 | #define MIRROR_COUNT 3 10 | 11 | // Utility functions 12 | static size_t RoundUpToPageSize(size_t n) 13 | { 14 | return round_page(n); 15 | } 16 | 17 | static void *RoundDownToPageSize(void *ptr) 18 | { 19 | return (void *)trunc_page((intptr_t)ptr); 20 | } 21 | 22 | // Class implementation 23 | @implementation MAMirroredQueue 24 | { 25 | char *_buf; 26 | size_t _bufSize; 27 | BOOL _allocationLocked; 28 | 29 | char *_readPointer; 30 | char *_writePointer; 31 | } 32 | 33 | - (void)dealloc 34 | { 35 | if(_buf) 36 | free_mirrored(_buf, _bufSize, MIRROR_COUNT); 37 | } 38 | 39 | - (size_t)availableBytes 40 | { 41 | ptrdiff_t amount = _writePointer - _readPointer; 42 | 43 | if(amount < 0) 44 | amount += _bufSize; 45 | else if((size_t)amount > _bufSize) 46 | amount -= _bufSize; 47 | 48 | return amount; 49 | } 50 | 51 | - (void *)readPointer 52 | { 53 | return _readPointer; 54 | } 55 | 56 | - (void)advanceReadPointer: (size_t)howmuch 57 | { 58 | _readPointer += howmuch; 59 | 60 | if((size_t)(_readPointer - _buf) >= _bufSize) 61 | { 62 | _readPointer -= _bufSize; 63 | __sync_sub_and_fetch(&_writePointer, _bufSize); 64 | } 65 | } 66 | 67 | - (BOOL)ensureWriteSpace: (size_t)howmuch 68 | { 69 | size_t contentLength = [self availableBytes]; 70 | if(howmuch <= _bufSize - contentLength) 71 | return YES; 72 | else if(_allocationLocked) 73 | return NO; 74 | 75 | // else reallocate 76 | size_t newBufferLength = RoundUpToPageSize(contentLength + howmuch); 77 | char *newBuf = allocate_mirrored(newBufferLength, MIRROR_COUNT); 78 | 79 | if(_bufSize > 0) 80 | { 81 | char *copyStart = RoundDownToPageSize(_readPointer); 82 | size_t copyLength = RoundUpToPageSize(_writePointer - copyStart); 83 | 84 | vm_copy(mach_task_self(), (vm_address_t)copyStart, copyLength, (vm_address_t)newBuf); 85 | 86 | char *newReadPointer = newBuf + (_readPointer - copyStart); 87 | if(*newReadPointer != *_readPointer) 88 | abort(); 89 | 90 | free_mirrored(_buf, _bufSize, MIRROR_COUNT); 91 | _readPointer = newReadPointer; 92 | _writePointer = _readPointer + contentLength; 93 | } 94 | else 95 | { 96 | _readPointer = newBuf; 97 | _writePointer = newBuf; 98 | } 99 | 100 | _buf = newBuf; 101 | _bufSize = newBufferLength; 102 | 103 | return YES; 104 | } 105 | 106 | - (void *)writePointer 107 | { 108 | return _writePointer; 109 | } 110 | 111 | - (void)advanceWritePointer: (size_t)howmuch 112 | { 113 | __sync_add_and_fetch(&_writePointer, howmuch); 114 | } 115 | 116 | - (void)lockAllocation 117 | { 118 | _allocationLocked = YES; 119 | } 120 | 121 | - (void)unlockAllocation 122 | { 123 | _allocationLocked = NO; 124 | } 125 | 126 | // UNIX-like compatibility wrappers 127 | - (size_t)read: (void *)buf count: (size_t)howmuch 128 | { 129 | size_t toRead = MIN(howmuch, [self availableBytes]); 130 | memcpy(buf, [self readPointer], toRead); 131 | [self advanceReadPointer: toRead]; 132 | return toRead; 133 | } 134 | 135 | - (size_t)write: (const void *)buf count: (size_t)howmuch 136 | { 137 | if(_allocationLocked) 138 | howmuch = MIN(howmuch, _bufSize - [self availableBytes]); 139 | else 140 | [self ensureWriteSpace: howmuch]; 141 | 142 | memcpy([self writePointer], buf, howmuch); 143 | [self advanceWritePointer: howmuch]; 144 | return howmuch; 145 | } 146 | 147 | @end 148 | 149 | // Test methods 150 | @implementation MAMirroredQueue (Testing) 151 | 152 | static void fail(const char *fmt, ...) 153 | { 154 | va_list args; 155 | va_start(args, fmt); 156 | 157 | vfprintf(stderr, fmt, args); 158 | fprintf(stderr, "\n"); 159 | 160 | va_end(args); 161 | } 162 | 163 | static void check_equal(MAMirroredQueue *queue, NSData *auxQueue) 164 | { 165 | const unsigned char *buf1 = [queue readPointer]; 166 | const unsigned char *buf2 = [auxQueue bytes]; 167 | 168 | size_t length = [queue availableBytes]; 169 | for(size_t i = 0; i < length; i++) 170 | if(buf1[i] != buf2[i]) 171 | fail("bytes don't match, %d != %d at index %lu", buf1[i], buf2[i], (long)i); 172 | } 173 | 174 | + (void)testThreaded: (int)iterCount 175 | { 176 | unsigned short seed[3] = { 0 }; 177 | 178 | NSLock *queueLock = nil;//[[NSLock alloc] init]; 179 | 180 | for(int iter = 0; iter < iterCount; iter++) 181 | @autoreleasepool 182 | { 183 | unsigned short *seedPtr1 = (unsigned short[]) { nrand48(seed), nrand48(seed), nrand48(seed) }; 184 | unsigned short *seedPtr2 = (unsigned short[]) { nrand48(seed), nrand48(seed), nrand48(seed) }; 185 | 186 | NSUInteger targetLength = nrand48(seed) % 1024 * 1024 + 1; 187 | 188 | MAMirroredQueue *queue = [[MAMirroredQueue alloc] init]; 189 | [queue ensureWriteSpace: 10240]; 190 | [queue lockAllocation]; 191 | 192 | NSMutableData *inData = [NSMutableData data]; 193 | NSMutableData *outData = [NSMutableData data]; 194 | 195 | dispatch_group_t group = dispatch_group_create(); 196 | 197 | dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ 198 | while([inData length] < targetLength) 199 | @autoreleasepool 200 | { 201 | unsigned len = nrand48(seedPtr1) % 1024 + 1; 202 | unsigned remaining = targetLength - [inData length]; 203 | len = MIN(len, remaining); 204 | 205 | char buf[len]; 206 | for(unsigned i = 0; i < len; i++) 207 | buf[i] = nrand48(seedPtr1); 208 | 209 | [queueLock lock]; 210 | while(![queue ensureWriteSpace: len]) 211 | { 212 | [queueLock unlock]; 213 | usleep(1); 214 | [queueLock lock]; 215 | } 216 | 217 | memcpy([queue writePointer], buf, len); 218 | [queue advanceWritePointer: len]; 219 | [queueLock unlock]; 220 | 221 | [inData appendBytes: buf length: len]; 222 | } 223 | }); 224 | dispatch_group_async(group, dispatch_get_global_queue(0, 0), ^{ 225 | while([outData length] < targetLength) 226 | @autoreleasepool 227 | { 228 | unsigned len = nrand48(seedPtr2) % 10240 + 1; 229 | [queueLock lock]; 230 | unsigned available = [queue availableBytes]; 231 | [queueLock unlock]; 232 | len = MIN(len, available); 233 | 234 | if(len > 0) 235 | { 236 | [queueLock lock]; 237 | [outData appendBytes: [queue readPointer] length: len]; 238 | [queue advanceReadPointer: len]; 239 | [queueLock unlock]; 240 | } 241 | } 242 | }); 243 | 244 | dispatch_group_wait(group, DISPATCH_TIME_FOREVER); 245 | dispatch_release(group); 246 | 247 | if(![inData isEqual: outData]) 248 | fail("Datas not equal!"); 249 | } 250 | } 251 | 252 | + (void)testNormal: (int)iterCount 253 | { 254 | unsigned short seed[3] = { 0 }; 255 | 256 | for(int iter = 0; iter < iterCount; iter++) 257 | { 258 | MAMirroredQueue *queue = [[MAMirroredQueue alloc] init]; 259 | NSMutableData *auxQueue = [NSMutableData data]; 260 | 261 | int readStart = nrand48(seed) % 100 + 1; 262 | int writeStop = nrand48(seed) % 1000 + readStart; 263 | int maxLength = nrand48(seed) % 65536; 264 | for(int i = 0; i < writeStop || [queue availableBytes] > 0; i++) 265 | { 266 | BOOL write = (i < readStart || (nrand48(seed) % 2)) && i < writeStop; 267 | 268 | size_t length = nrand48(seed) % (maxLength + 1); 269 | 270 | if(write) 271 | { 272 | [queue ensureWriteSpace: length]; 273 | check_equal(queue, auxQueue); 274 | char *buf = [queue writePointer]; 275 | for(unsigned j = 0; j < length; j++) 276 | { 277 | unsigned char byte = nrand48(seed); 278 | buf[j] = byte; 279 | [auxQueue appendBytes: &byte length: 1]; 280 | } 281 | check_equal(queue, auxQueue); 282 | [queue advanceWritePointer: length]; 283 | check_equal(queue, auxQueue); 284 | } 285 | else 286 | { 287 | length = MIN(length, [queue availableBytes]); 288 | check_equal(queue, auxQueue); 289 | [queue advanceReadPointer: length]; 290 | [auxQueue replaceBytesInRange: NSMakeRange(0, length) withBytes: NULL length: 0]; 291 | } 292 | 293 | if([queue availableBytes] != [auxQueue length]) 294 | fail("lengths don't match: %lu != %lu", (long)[queue availableBytes], (long)[auxQueue length]); 295 | 296 | check_equal(queue, auxQueue); 297 | } 298 | } 299 | } 300 | 301 | + (void)runTests 302 | { 303 | [self testNormal: 10]; 304 | [self testThreaded: 1000]; 305 | } 306 | 307 | @end 308 | --------------------------------------------------------------------------------