Java-Gaming.org    
Featured games (91)
games approved by the League of Dukes
Games in Showcase (581)
games submitted by our members
Games in WIP (500)
games currently in development
News: Read the Java Gaming Resources, or peek at the official Java tutorials
 
    Home     Help   Search   Login   Register   
Pages: [1]
  ignore  |  Print  
  a general approach for nio buffer pooling  (Read 3493 times)
0 Members and 1 Guest are viewing this topic.
Offline gouessej

« In padded room »



TUER


« Posted 2012-01-17 00:35:54 »

Hi

Riven's advises helped me a little bit to implement hierarchical streaming but I wonder whether there exists a general approach for NIO buffer pooling. My main concerns are fragmentation and thread safety. I might use only a single pool per thread but how can I handle smartly the problem of fragmentation?

Is sun.nio.ch.Util useful to solve this problem?

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #1 - Posted 2012-01-17 10:30:01 »

IIRC I never published code that does ByteBuffer pooling? I think you refer to this code that does slice a large ByteBuffer into smaller ones (to avoid the 4K overhead per malloc). Once the large ByteBuffer is completely consumed (as in: you can't slice off the demanded N bytes), it simply allocates another large ByteBuffer and starts slicing that. I let the GC figure out when all the sliced buffers are not referenced any longer, and the large ByteBuffer will automatically be deallocated.


If you however want true pooling, I advice you to make a List<ByteBuffer>[], containing power-of-two sized ByteBuffers:
1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
29  
30  
31  
32  
33  
34  
35  
36  
37  
38  
39  
40  
41  
42  
43  
44  
45  
46  
47  
48  
49  
50  
51  
52  
53  
54  
55  
56  
57  
58  
59  
60  
61  
62  
63  
64  
65  
66  
67  
68  
69  
70  
71  
public class ByteBufferPool {
   
   final List<ByteBuffer>[] potBuffers;

   public ByteBufferPool()
   {
      potBuffers= (List<ByteBuffer>[]) new List[32];
      for (int i = 0; i < potBuffers.length; i++) {
         potBuffers[i] = new ArrayList<ByteBuffer>();
      }
   }
   
   public ByteBuffer aquire(int bytes) {
      int alloc = allocSize(bytes);
      int index = Integer.numberOfTrailingZeros(alloc);
      List<ByteBuffer> list = potBuffers[index];
     
      ByteBuffer bb = list.isEmpty() ? create(alloc) : list.remove(list.size() - 1);
      bb.position(0).limit(bytes);

      // fill with zeroes to ensure deterministic behavior upon handling 'uninitialized' data
     for (int i = 0, n = bb.remaining(); i < n; i++) {
         bb.put(i, (byte) 0);
      }

      return bb;
   }
   
   public void release(ByteBuffer buffer) {
      int alloc = allocSize(buffer.capacity());
      if (buffer.capacity() != alloc) {
         throw new IllegalArgumentException("buffer capacity not a power of two");
      }
      int index = Integer.numberOfTrailingZeros(alloc);
      potBuffers[index].add(buffer);
   }

   public void flush() {
      for (int i = 0; i < potBuffers.length; i++) {
         potBuffers[i].clear();
      }
   }
   
   private static int LARGE_SIZE  = 1024 * 1024;
   private ByteBuffer largeBuffer = malloc(LARGE_SIZE);
   
   private ByteBuffer create(int bytes) {
      if (bytes > LARGE_SIZE)
         return malloc(bytes);
     
      if (bytes > largeBuffer.remaining()) {
         largeBuffer = malloc(LARGE_SIZE);
      }
     
      largeBuffer.limit(largeBuffer.position() + bytes);
      ByteBuffer bb = largeBuffer.slice();
      largeBuffer.position(largeBuffer.limit());      
      return bb;
   }
   
   private static ByteBuffer malloc(int bytes) {
      return ByteBuffer.allocateDirect(bytes).order(ByteOrder.nativeOrder());
   }
   
   private static int allocSize(int bytes) {
      if (bytes <= 0) {
         throw new IllegalArgumentException("attempted to allocate zero bytes");
      }
      return (bytes > 1) ? Integer.highestOneBit(bytes - 1) << 1 : 1;
   }
}

Code is completely untested, not even compiled.

Using POT buffer sizes solves a lot of problems. In case you worry about fragmentation, simply flush() the pool every once in a while, so that the GC can cleanup after you.

Threadsafety is easily obtained by either using ThreadLocals or extending the class in the example code and making all public methods synchronized. Both (more or less) hurt performance, so you might want to read up on lockfree datastructures. Keep in mind though that it's unlikely that it will be your bottleneck, unless you aquire/release in some inner loop.

1  
2  
3  
4  
5  
6  
7  
8  
9  
10  
11  
12  
13  
14  
15  
16  
17  
18  
19  
20  
21  
22  
23  
24  
25  
26  
27  
28  
   public static ByteBufferPool synced(final Object mutex) {
      if (mutex == null) {
         throw new NullPointerException();
      }

      return new ByteBufferPool() {
         @Override
         public ByteBuffer aquire(int bytes) {
            synchronized (mutex) {
               return super.aquire(bytes);
            }
         }
         
         @Override
         public void release(ByteBuffer buffer) {
            synchronized (mutex) {
               super.release(buffer);
            }
         }
         
         @Override
         public void flush() {
            synchronized (mutex) {
               super.flush();
            }
         }
      };
   }

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline gouessej

« In padded room »



TUER


« Reply #2 - Posted 2012-02-23 21:58:33 »

Hi

Thank you Riven once more.

Maybe I'm silly. I wonder if nio buffer pooling is the right answer to my problems. I have recently thought about a smarter approach. Actually, when a player finishes the first level and wants to go to the second one, some models will be used, some models won't. Of course, I use direct NIO buffers to store their data (vertices, texture coordinates). My suggestion is this further :
- just before going to another level, compare the factories used by the previous level and the factories used by the next level
- ask the factories used in the previous level but not in the next level to explicitly release all native resources (for example by using the cleaners of their direct NIO buffers to destroy them)
- ask the factories used in the next level but not in the previous level to load their models
- don't ask the factories used both in the previous level and in the next level to explicitly release all native resources

I saw many C++ programmers using some kind of resource manager as a sort of catch-all, I would like not to do the same thing. Is my latest suggestion completely stupid?

Games published by our own members! Check 'em out!
Legends of Yore - The Casual Retro Roguelike
Offline Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #3 - Posted 2012-02-23 23:17:59 »

Buffer pooling is only needed when you have a high allocation rate of small buffers.

A 'per level' allocation certainly doesn't fit this picture. Further, a specialized approach will always be better than a generic one. If you think you can do better, you probably can. But do you actually need any buffer pooling, did it show up as a bottleneck? If so, if the generic solution fast enough anyway? If not, are you willing to actually fix that bottleneck, or work on logic/content that the player actually cares about.

TL;DR:
premature optimization is the premature root of premature evil.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline gouessej

« In padded room »



TUER


« Reply #4 - Posted 2012-02-23 23:45:16 »

It's not premature optimization, JFPSM already uses 1.5 GB, I fixed theOutOfMemoryError some years ago by using indirect NIO buffers when direct NIO buffers were not absolutely necessary.

The alpha version of TUER only uses tens of MB, the prebeta version already uses more than 100 MB whereas it has less features  Shocked

If I destroy a NIO buffer, can it cause some trouble with OpenGL if it was used in a VBO (see glBufferData)?

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #5 - Posted 2012-02-24 00:23:54 »

If I destroy a NIO buffer, can it cause some trouble with OpenGL if it was used in a VBO?
Yes, it can crash the entire process. This is why libraries like JOGL and LWJGL keep a reference to the buffer you passed to those methods. Before that was done, the GC could free that memory, resulting in an access violation in the driver.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline gouessej

« In padded room »



TUER


« Reply #6 - Posted 2012-02-24 19:04:25 »

If I destroy a NIO buffer, can it cause some trouble with OpenGL if it was used in a VBO?
Yes, it can crash the entire process. This is why libraries like JOGL and LWJGL keep a reference to the buffer you passed to those methods. Before that was done, the GC could free that memory, resulting in an access violation in the driver.
Ok. Sven explained to me that he had to modify something. glDeleteBuffers will remove the kept references from the cache so that I can do what I planned. Just calling clear() on an object containing a direct NIO buffer does not guarantee that Java will immediately releases the resources used on the native heap. My approach will probably only work with signed applications.

Offline Riven
« League of Dukes »

JGO Overlord


Medals: 605
Projects: 4
Exp: 16 years


Hand over your head.


« Reply #7 - Posted 2012-02-24 19:07:26 »

My approach will probably only work with signed applications.

Hi, appreciate more people! Σ ♥ = ¾
Learn how to award medals... and work your way up the social rankings
Offline gouessej

« In padded room »



TUER


« Reply #8 - Posted 2012-02-24 22:11:42 »

Thank you for this complementary information. It would be strange that Java would allow to release native memory without permission.

Some programmers of the Netty project tried to allocate and deallocate buffers "manually" with sun.misc.Unsafe but it seems slower than using direct NIO buffers.

Pages: [1]
  ignore  |  Print  
 
 
You cannot reply to this message, because it is very, very old.

 

Add your game by posting it in the WIP section,
or publish it in Showcase.

The first screenshot will be displayed as a thumbnail.

xsi3rr4x (56 views)
2014-04-15 18:08:23

BurntPizza (54 views)
2014-04-15 03:46:01

UprightPath (67 views)
2014-04-14 17:39:50

UprightPath (50 views)
2014-04-14 17:35:47

Porlus (67 views)
2014-04-14 15:48:38

tom_mai78101 (91 views)
2014-04-10 04:04:31

BurntPizza (152 views)
2014-04-08 23:06:04

tom_mai78101 (248 views)
2014-04-05 13:34:39

trollwarrior1 (205 views)
2014-04-04 12:06:45

CJLetsGame (212 views)
2014-04-01 02:16:10
List of Learning Resources
by SHC
2014-04-18 03:17:39

List of Learning Resources
by Longarmx
2014-04-08 03:14:44

Good Examples
by matheus23
2014-04-05 13:51:37

Good Examples
by Grunnt
2014-04-03 15:48:46

Good Examples
by Grunnt
2014-04-03 15:48:37

Good Examples
by matheus23
2014-04-01 18:40:51

Good Examples
by matheus23
2014-04-01 18:40:34

Anonymous/Local/Inner class gotchas
by Roquen
2014-03-11 15:22:30
java-gaming.org is not responsible for the content posted by its members, including references to external websites, and other references that may or may not have a relation with our primarily gaming and game production oriented community. inquiries and complaints can be sent via email to the info‑account of the company managing the website of java‑gaming.org
Powered by MySQL Powered by PHP Powered by SMF 1.1.18 | SMF © 2013, Simple Machines | Managed by Enhanced Four Valid XHTML 1.0! Valid CSS!