Skip to content

PackedWeaver Implementation Details

Adrian Papari edited this page Jan 24, 2015 · 3 revisions

Internal representation

Reference component:

@PackedWeaver
public class PackedWeaverReference extends Component {
    public float x;
    public float y;

    public void set(Vec2f vec) {
        this.x = vec.x;
        this.y = vec.y;
    }
}

The packed component looks something like this after it's been compiled:

public class PackedWeaverReference extends PackedComponent implements DisposedWithWorld {

    private int $stride;
    private static final int $_SIZE_OF = 8;
    private static Map<World, Bag<PackedWeaverReference>> $store = new IdentityHashMap<World, Bag<PackedWeaverReference>>();
    private ByteBuffer $data = null;
    private World $world;
    
    public PackedWeaverReference(World world) {
        this.$world = world;
        Bag<PackedWeaverReference> instances = $store.get(world);
        if (instances != null) {
            $data = instances.get(0).$data;
        } else {
            $data = ByteBuffer.allocateDirect(128 * $_SIZE_OF);
            
            instances = new Bag<PackedWeaverReference>();
            $store.put(world, instances);
        }
        
        instances.add(this);
    }

    @Override
    protected void forEntity(Entity e) {
        this.$stride = $_SIZE_OF * e.getId();
    }

    @Override
    protected void reset() {
        $data.putFloat($stride + 0, 0);
        $data.putFloat($stride + 4, 0);
    }
    
    @Override
    protected void ensureCapacity(int id) {
        int requested = (1 + id) * $_SIZE_OF;
        if ($data.capacity() < requested)
            $grow(2 * Math.max($data.capacity(), requested));
    }
    
    private void $grow(int capacity) {
        ByteBuffer newBuffer = ByteBuffer.allocateDirect(capacity);
        for (int i = 0, s = $data.capacity(); s > i; i++)
            newBuffer.put(i, $data.get(i));
        
        for (PackedWeaverReference ref : $store.get($world))
            ref.$data = newBuffer;
    }
    
    @Override
    public void free(World world) {
        $store.remove(world);
    }
    
    public float x() {
        return $data.getFloat($stride + 0);
    }
    
    public float y() {
        return $data.getFloat($stride + 4);
    }
    
    public void x(float value) {
        $data.putFloat($stride + 0, value);
    }
    
    public void y(float value) {
        $data.putFloat($stride + 4, value);
    }
    
    public void set(Vec2f v) {
        $data.putFloat($stride + 0, v.x());
        $data.putFloat($stride + 4, v.y);
    }
}

Performance

Take the results with a grain of salt. There are no guarantees that the benchmark is correct, but hopefully. If you know your way around benchmarking, feel free to take a look at the code.

All benchmarks run World#process - at every 100th iteration an entity is deleted and then recreated. All benchmarks have an additional system updating a single component per entity, except baseline - which only iterates all entities without actually reading/updating the component.

plain, pooled and packed work with normal com.artemis.Component', com.artemis.PooledComponent' and 'com.artemis.PackedComponent' respectively.

# VM invoker: /home/junkdog/opt/apps/jdk1.7.0_55/jre/bin/java

Benchmark 
c.a.ComponentTypeBenchmark.baseline_world     avgt        15       10.821        0.138    us/op
c.a.ComponentTypeBenchmark.packed_world       avgt        15       35.276        0.533    us/op
c.a.ComponentTypeBenchmark.plain_world        avgt        15       30.739        0.195    us/op
c.a.ComponentTypeBenchmark.pooled_world       avgt        15       28.426        0.043    us/op

Reflecting on the results:

  • The struct-like approach yields the worst performance; supposedly, due to the overhead from method calls vs direct field access.
  • EntitySystems don't internally impose any ordering on its entities - processing entities in ascending entityId order would reduce cache misses. #87 Fixed, improved performance throughout.
  • ByteBuffer - injected by @PackedWeaver - performs boundary checks when invoking get and put methods. Weaving with sun.misc.Unsafe should perform better. Support for choosing between java.nio.ByteBuffer and sun.misc.Unsafe is planned for a post-0.6.0 release: #86
  • @PooledComponent is likely to perform much worse in the real world. Be wary.

To manually run the benchmarks - from artemis' root folder:

mvn clean install
java -jar artemis-benchmark/target/microbenchmarks.jar -t 1 -f 5

To get less noisy results, consider:

  • Rebooting the computer
  • Stop any non-vital background processes
  • Disable all network adapters.
Clone this wiki locally