用于物理游戏的内存高效 AI 对象
Memory efficient AI objects for physics game
我正在 java 使用 box2d 创建一个物理游戏。
我正在编写一个 AI class 并希望确保我的数据尽可能高效地存储,同时考虑到内存对齐。
最小的增加可能会产生巨大的差异,因为我实际上是 运行 'as many AI objects as I can' 直到系统变慢 down.The 程序已经在碰撞检测上使用了大量内存,因为再一次,我希望能够支持尽可能多的代理商。
到目前为止,我所了解的是,最小的 Java 类型是 8 字节,对象被填充为 8 的倍数。我在布尔数组中构建了我的 AI 控件,代表运动:x+/- 1、y+/-1 和 clockwise/CCW 特定灯具的旋转。
由于 Java 没有布尔值的空设置,我将控件嵌套在具有布尔值 on_off 和 pos_neg 的命令对象中。对于移动和旋转,每个 'default' 动作(例如向右移动)我要处理大约 7 个命令对象。所以我为每个动作创建命令数组。
我的问题是:我这样做效率高吗?
我还没有最终确定设计,所以我不确定每个数组的大小。但是,考虑到内存对齐要求,我猜我至少会有 一些 填充,这最终被浪费了 memory.I正在考虑做一些事情,比如将对象大小缩小到适应填充限制,然后将来自多个对象的剩余数据推送到一个 'overflow' 对象中......或类似的东西。
这会加快速度吗?为什么或为什么不?
我也在考虑使用位集,虽然我认为我的命令对象可能已经达到了类似的结果,而且我被告知位移很慢。
public class Command {
boolean on_off = false;
boolean pos_neg = false;
}
public class DefaultMoves {
//Note: these arrays are 2d in the event multiples are necessary
//to complete a single action, I may change this later.
Command[][] mvRight =
{
{ new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(true, true), //moveX
new Command(false, false) //
},
};
Command[][] mvLeft =
{
{ new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(true, false), //moveX
new Command(false, false) //
},
};
}
这本来只是一条评论,但有点冗长,我不想把它写成 3 条评论。
由于这是另一个人的后续问题,我将从"Stop worrying about padding"开始。担心您如何存储数据。
而且,如果您担心 space 您的东西需要多少,请分配一个包含 7 个对象的数组,而不是 7 个单独的对象。我确定 Java 在每次分配中都有开销。在典型的 C 或 C++ 实现中,使用 new
或 malloc
的每个分配占用超出实际分配数据大小的 16-32 字节,并且大小四舍五入为 16 或 32 字节。在 java 中有一个建议 here 一个对象的内存开销是 8 个字节——这可能不适用于所有 Java 虚拟机和实现。
此外,所有 space&time 优化都是 space 和时间 [几乎总是至少] 之间的折衷,因此以更紧凑的形式存储数据将花费时间来节省space。例如,我可以认为将 on_off
和 pos_neg
对作为较大整数结构中的两位。所以你的 7 个命令将存储在一个整数中。但是,现在您必须进行轮班和屏蔽才能获得正确的数据。同样,如果你要存储一些东西,也可以移动和 oring。 (我把它写成 C,因为我不太了解 Java)。
/* This is big enough for 16 pairs of on_off and pos_neg */
/* In a pair of bits, bit 0 = on_off, bit 1 = pos_neg */
uint32_t cmdData;
/* Get values of on_off and pos_neg for element number n */
void getData(int n, bool& on_off, bool& pos_neg)
{
uint32_t shifted = cmdData >> (2 * n);
on_off = (shifted & 1) != 0;
pos_neg = (shifted & 2) != 0;
}
/* Store values for element n */
void setData(int n, bool on_off, bool pos_neg)
{
uint32_t bits = (int)on_off + (2 * (int)pos_neg);
uint32_t mask = 3 << (n * 2);
cmdData &= ~mask; /* Clear bits */
cmdData |= bits << (n * 2);
}
如您所见,这可以更有效地存储数据,因为我们可以在 4 个字节中存储 16 对 {on_off, pos_neg}
,而不是每个(可能)占用一个字节。但是要做到每一个,你每次都必须做一些额外的操作(而且代码会变得更混乱)。这是否是 "worth having" 很大程度上取决于具体情况,您访问它们的频率与系统内存的不足程度(假设这些对象的内存使用是导致问题的原因 - 如果您有 100 个命令结构和 40000000 个使用这些命令的对象,那么这些命令就不会成为问题)。
我为 direction/movement 存储命令的方式可能是两个整数值(int8_t
[byte
in java] 如果 space 是紧),按住 +1
用于向右或向下移动,-1
用于向左或向上移动。这不是最紧凑的形式,但它使新位置的访问和计算变得容易。
这对可以用来描述所有可能的方向:
struct direction
{
int x, y;
};
direction directions[] =
{
{ 0, 0 }, // Don't move.
{ 0, 1 }, // Right.
{ 0, -1 }, // Left.
{ 1, 0 }, // Down.
{ -1, 0 }, // Up.
};
如果你也想沿对角线移动,则必须添加另外四对 { 1, 1 }, {-1, 1}
等的组合
这同样适用于可以移动的对象,作为一对 xDir, yDir
值。
但这里的关键是你首先要很好地理解什么更重要:space 或计算。从中找出哪些对象占用了大部分 space(计数最高的对象)。摆弄一个或几十个对象的大小不会有什么大的不同,你有数百万个)。如果 space 不是问题(并且公平地说,在具有千兆字节 RAM 的系统中编写有效使用足够大量数据的代码真的很难 - 通常是 CPU 用完了如果你对每一帧的每个对象都做一些事情,内存耗尽之前的速度。
手提箱类比:
想象一下,您有一个手提箱,其宽度正好可以容纳 4 个小盒子 [以及长度上的任何数字 - 这是一个奇怪的手提箱!],并且您有更大的盒子,分别是 1、2、3 或 4小盒子的单位。这些盒子是用 "velcro" 制作的,所以它们可以粘在一起,可以随意拆分,但你必须记下哪些属于一起,并且每次你 "split" 或 "put back together"单位,需要额外的时间。
如果你想偷懒,简单点,你只需将三盒装的装进手提箱,每盒旁边放一个空的 space。
1 2 3 4
a a a x
b b b x
c c c x
d d d x
等等。
如果你想把它包紧,你拿一个3单元的盒子,然后切下一个单元的一个单元,把它贴在第一个旁边,然后剩下的两个单元放在下一个space,然后从下一个切下两个单位的一块,然后将其贴在第 2 包旁边,依此类推。
1 2 3 4
a a a b
b b c c
c d d d
现在,你已经少用了 25% space 来存储它们,但是你花了时间拆分它们,你必须再次花时间将它们以三个为单位取出,以便以后需要使用数据。
现在,想象一下,你把东西放进手提箱是有报酬的,而且你每放一件东西就得到报酬,你会选择哪种方式?
然后考虑一下,你不是按件付钱,你必须为手提箱付钱space(因为你是公司的老板)。现在你想尽可能多地挤进 space 。但这需要额外的时间,对吧?如果手提箱很贵,那么它可能是值得的。如果不是那么贵,您可能更愿意节省时间 space。这是妥协。
[我们可以用更现实的 8、32 或 64 为单位做同样的事情,但我认为这只会让它更难阅读,而且肯定会更难打字]
我正在 java 使用 box2d 创建一个物理游戏。
我正在编写一个 AI class 并希望确保我的数据尽可能高效地存储,同时考虑到内存对齐。
最小的增加可能会产生巨大的差异,因为我实际上是 运行 'as many AI objects as I can' 直到系统变慢 down.The 程序已经在碰撞检测上使用了大量内存,因为再一次,我希望能够支持尽可能多的代理商。
到目前为止,我所了解的是,最小的 Java 类型是 8 字节,对象被填充为 8 的倍数。我在布尔数组中构建了我的 AI 控件,代表运动:x+/- 1、y+/-1 和 clockwise/CCW 特定灯具的旋转。
由于 Java 没有布尔值的空设置,我将控件嵌套在具有布尔值 on_off 和 pos_neg 的命令对象中。对于移动和旋转,每个 'default' 动作(例如向右移动)我要处理大约 7 个命令对象。所以我为每个动作创建命令数组。
我的问题是:我这样做效率高吗?
我还没有最终确定设计,所以我不确定每个数组的大小。但是,考虑到内存对齐要求,我猜我至少会有 一些 填充,这最终被浪费了 memory.I正在考虑做一些事情,比如将对象大小缩小到适应填充限制,然后将来自多个对象的剩余数据推送到一个 'overflow' 对象中......或类似的东西。
这会加快速度吗?为什么或为什么不?
我也在考虑使用位集,虽然我认为我的命令对象可能已经达到了类似的结果,而且我被告知位移很慢。
public class Command {
boolean on_off = false;
boolean pos_neg = false;
}
public class DefaultMoves {
//Note: these arrays are 2d in the event multiples are necessary
//to complete a single action, I may change this later.
Command[][] mvRight =
{
{ new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(true, true), //moveX
new Command(false, false) //
},
};
Command[][] mvLeft =
{
{ new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(false, false), //
new Command(true, false), //moveX
new Command(false, false) //
},
};
}
这本来只是一条评论,但有点冗长,我不想把它写成 3 条评论。
由于这是另一个人的后续问题,我将从"Stop worrying about padding"开始。担心您如何存储数据。
而且,如果您担心 space 您的东西需要多少,请分配一个包含 7 个对象的数组,而不是 7 个单独的对象。我确定 Java 在每次分配中都有开销。在典型的 C 或 C++ 实现中,使用 new
或 malloc
的每个分配占用超出实际分配数据大小的 16-32 字节,并且大小四舍五入为 16 或 32 字节。在 java 中有一个建议 here 一个对象的内存开销是 8 个字节——这可能不适用于所有 Java 虚拟机和实现。
此外,所有 space&time 优化都是 space 和时间 [几乎总是至少] 之间的折衷,因此以更紧凑的形式存储数据将花费时间来节省space。例如,我可以认为将 on_off
和 pos_neg
对作为较大整数结构中的两位。所以你的 7 个命令将存储在一个整数中。但是,现在您必须进行轮班和屏蔽才能获得正确的数据。同样,如果你要存储一些东西,也可以移动和 oring。 (我把它写成 C,因为我不太了解 Java)。
/* This is big enough for 16 pairs of on_off and pos_neg */
/* In a pair of bits, bit 0 = on_off, bit 1 = pos_neg */
uint32_t cmdData;
/* Get values of on_off and pos_neg for element number n */
void getData(int n, bool& on_off, bool& pos_neg)
{
uint32_t shifted = cmdData >> (2 * n);
on_off = (shifted & 1) != 0;
pos_neg = (shifted & 2) != 0;
}
/* Store values for element n */
void setData(int n, bool on_off, bool pos_neg)
{
uint32_t bits = (int)on_off + (2 * (int)pos_neg);
uint32_t mask = 3 << (n * 2);
cmdData &= ~mask; /* Clear bits */
cmdData |= bits << (n * 2);
}
如您所见,这可以更有效地存储数据,因为我们可以在 4 个字节中存储 16 对 {on_off, pos_neg}
,而不是每个(可能)占用一个字节。但是要做到每一个,你每次都必须做一些额外的操作(而且代码会变得更混乱)。这是否是 "worth having" 很大程度上取决于具体情况,您访问它们的频率与系统内存的不足程度(假设这些对象的内存使用是导致问题的原因 - 如果您有 100 个命令结构和 40000000 个使用这些命令的对象,那么这些命令就不会成为问题)。
我为 direction/movement 存储命令的方式可能是两个整数值(int8_t
[byte
in java] 如果 space 是紧),按住 +1
用于向右或向下移动,-1
用于向左或向上移动。这不是最紧凑的形式,但它使新位置的访问和计算变得容易。
这对可以用来描述所有可能的方向:
struct direction
{
int x, y;
};
direction directions[] =
{
{ 0, 0 }, // Don't move.
{ 0, 1 }, // Right.
{ 0, -1 }, // Left.
{ 1, 0 }, // Down.
{ -1, 0 }, // Up.
};
如果你也想沿对角线移动,则必须添加另外四对 { 1, 1 }, {-1, 1}
等的组合
这同样适用于可以移动的对象,作为一对 xDir, yDir
值。
但这里的关键是你首先要很好地理解什么更重要:space 或计算。从中找出哪些对象占用了大部分 space(计数最高的对象)。摆弄一个或几十个对象的大小不会有什么大的不同,你有数百万个)。如果 space 不是问题(并且公平地说,在具有千兆字节 RAM 的系统中编写有效使用足够大量数据的代码真的很难 - 通常是 CPU 用完了如果你对每一帧的每个对象都做一些事情,内存耗尽之前的速度。
手提箱类比:
想象一下,您有一个手提箱,其宽度正好可以容纳 4 个小盒子 [以及长度上的任何数字 - 这是一个奇怪的手提箱!],并且您有更大的盒子,分别是 1、2、3 或 4小盒子的单位。这些盒子是用 "velcro" 制作的,所以它们可以粘在一起,可以随意拆分,但你必须记下哪些属于一起,并且每次你 "split" 或 "put back together"单位,需要额外的时间。
如果你想偷懒,简单点,你只需将三盒装的装进手提箱,每盒旁边放一个空的 space。
1 2 3 4
a a a x
b b b x
c c c x
d d d x
等等。
如果你想把它包紧,你拿一个3单元的盒子,然后切下一个单元的一个单元,把它贴在第一个旁边,然后剩下的两个单元放在下一个space,然后从下一个切下两个单位的一块,然后将其贴在第 2 包旁边,依此类推。
1 2 3 4
a a a b
b b c c
c d d d
现在,你已经少用了 25% space 来存储它们,但是你花了时间拆分它们,你必须再次花时间将它们以三个为单位取出,以便以后需要使用数据。
现在,想象一下,你把东西放进手提箱是有报酬的,而且你每放一件东西就得到报酬,你会选择哪种方式?
然后考虑一下,你不是按件付钱,你必须为手提箱付钱space(因为你是公司的老板)。现在你想尽可能多地挤进 space 。但这需要额外的时间,对吧?如果手提箱很贵,那么它可能是值得的。如果不是那么贵,您可能更愿意节省时间 space。这是妥协。
[我们可以用更现实的 8、32 或 64 为单位做同样的事情,但我认为这只会让它更难阅读,而且肯定会更难打字]