原始值的映射替代

时间:2017-01-18 17:57:40

标签: java java-8 primitive

我对我的应用程序进行了一些分析,其中一个结果发现堆上大约18%的内存被Microsoft (R) Build Engine version 4.6.1586.0 [Microsoft .NET Framework, version 4.0.30319.42000] Copyright (C) Microsoft Corporation. All rights reserved. Building the projects in this solution one at a time. To enable parallel build, please add the "/m" switch. Build started 18/01/2017 17:00:19. Project "D:\Jenkins\workspace\hcg\database.solution\database.solution.sln" on node 1 (default targets). ValidateSolutionConfiguration: Building solution configuration "Debug|Any CPU". Project "D:\Jenkins\workspace\hcg\database.solution\database.solution.sln" (1) is building "D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj" (2) on node 1 (default targets). C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets(983,5): warning MSB3644: The reference assemblies for framework ".NETFramework,Version=v4.5.2" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend. [D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj] GenerateSqlTargetFrameworkMoniker: Skipping target "GenerateSqlTargetFrameworkMoniker" because all output files are up-to-date with respect to the input files. CoreCompile: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Csc.exe /noconfig /nowarn:1701,1702 /nostdlib+ /platform:x64 /errorreport:prompt /warn:4 /define:DEBUG;TRACE /highentropyva+ /reference:C:\Windows\Microsoft.NET\Framework64\v4.0.30319\mscorlib.dll /debug+ /debug:full /optimize- /out:obj\Debug\database.solution.dll /subsystemversion:6.00 /target:library /warnaserror- /utf8output "C:\Users\svc_jenkins\AppData\Local\Temp\.NETFramework,Version=v4.5.2.SqlClrAttributes.cs" D:\Nuget\Microsoft.Data.Tools.Msbuild\lib\net40\Microsoft.Data.Tools.Schema.SqlTasks.targets(477,5): error MSB4062: The "SqlModelResolutionTask" task could not be loaded from the assembly D:\Nuget\Microsoft.Data.Tools.Msbuild\lib\net40\Microsoft.Data.Tools.Schema.Tasks.Sql.dll. Could not load file or assembly 'Microsoft.Build.Utilities.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask. [D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj] Done Building Project "D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj" (default targets) -- FAILED. Done Building Project "D:\Jenkins\workspace\hcg\database.solution\database.solution.sln" (default targets) -- FAILED. Build FAILED. "D:\Jenkins\workspace\hcg\database.solution\database.solution.sln" (default target) (1) -> "D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj" (default target) (2) -> (GetReferenceAssemblyPaths target) -> C:\Windows\Microsoft.NET\Framework64\v4.0.30319\Microsoft.Common.targets(983,5): warning MSB3644: The reference assemblies for framework ".NETFramework,Version=v4.5.2" were not found. To resolve this, install the SDK or Targeting Pack for this framework version or retarget your application to a version of the framework for which you have the SDK or Targeting Pack installed. Note that assemblies will be resolved from the Global Assembly Cache (GAC) and will be used in place of reference assemblies. Therefore your assembly may not be correctly targeted for the framework you intend. [D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj] "D:\Jenkins\workspace\hcg\database.solution\database.solution.sln" (default target) (1) -> "D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj" (default target) (2) -> (_SetupSqlBuildInputs target) -> D:\Nuget\Microsoft.Data.Tools.Msbuild\lib\net40\Microsoft.Data.Tools.Schema.SqlTasks.targets(477,5): error MSB4062: The "SqlModelResolutionTask" task could not be loaded from the assembly D:\Nuget\Microsoft.Data.Tools.Msbuild\lib\net40\Microsoft.Data.Tools.Schema.Tasks.Sql.dll. Could not load file or assembly 'Microsoft.Build.Utilities.Core, Version=15.1.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a' or one of its dependencies. The system cannot find the file specified. Confirm that the <UsingTask> declaration is correct, that the assembly and all its dependencies are available, and that the task contains a public class that implements Microsoft.Build.Framework.ITask. [D:\Jenkins\workspace\hcg\database.solution\database.solution\database.solution.sqlproj] 1 Warning(s) 1 Error(s) Time Elapsed 00:00:00.51 类型的对象使用。事实证明,这些对象是Double s中的值,我不能使用原始类型。

我的理由是Map的原始类型比它的对象double消耗更少的内存。有没有办法让地图像数据结构一样,可以接受任何类型的键和原始Double作为值?

主要操作是:

  • 插入(可能只有一次)
  • 查找(按键包含)
  • 检索(按键)
  • 迭代

我拥有的典型地图是:

  • double
  • HashMap<T, HashMap<NodeData<T>, Double>> graph(虽然不是双倍值)
  • HashMap<Point2D, Boolean> onSea

所有与Java 8一起使用。

附录

我主要对那些有这些类型地图解决方案的框架不感兴趣,但对解决这些问题时需要考虑的内容感兴趣。如果您愿意,任何此类框架背后的概念/想法/方法是什么。或者解决方案也可能在另一个层面,其中地图被替换为像@Ilmari Karonen在他的答案中指出的特定模式的对象。

5 个答案:

答案 0 :(得分:15)

Eclipse Collectionsobjectprimitive maps,并且两者都有Mutable和Immutable版本。

MutableObjectDoubleMap<String> doubleMap = ObjectDoubleMaps.mutable.empty();
doubleMap.put("1", 1.0d);
doubleMap.put("2", 2.0d);

MutableObjectBooleanMap<String> booleanMap = ObjectBooleanMaps.mutable.empty();
booleanMap.put("ok", true);

ImmutableObjectDoubleMap<String> immutableMap = doubleMap.toImmutable();
Assert.assertEquals(doubleMap, immutableMap);

通过调用MutableMap,我可以将ImmutableMap用作Eclipse集合中toImmutable的工厂,就像我在上面的示例中所做的那样。可变映射和不可变映射都共享一个公共父接口,在上面的MutableObjectDoubleMapImmutableObjectDoubleMap的情况下,它被命名为ObjectDoubleMap

Eclipse Collections还为库中的所有可变容器提供了同步和不可修改的版本。以下代码将为您提供一个围绕原始映射的同步视图。

MutableObjectDoubleMap<String> doubleMap = 
        ObjectDoubleMaps.mutable.<String>empty().asSynchronized();
doubleMap.put("1", 1.0d);
doubleMap.put("2", 2.0d);

MutableObjectBooleanMap<String> booleanMap = 
        ObjectBooleanMaps.mutable.<String>empty().asSynchronized();
booleanMap.put("ok", true);

几年前发布了大型地图的性能比较。

Large HashMap overview: JDK, FastUtil, Goldman Sachs, HPPC, Koloboke, Trove – January 2015 version

此后,GS Collections已迁移到Eclipse Foundation,现在是Eclipse Collections。

注意:我是Eclipse Collections的提交者。

答案 1 :(得分:12)

您正在寻找的是来自Object2DoubleOpenHashMapfastutil(具有较小内存占用和快速访问和插入的集合框架),它提供double getDouble(Object k)和{{3}类型的方法}。

例如:

// Create a Object2DoubleOpenHashMap instance
Object2DoubleMap<String> map = new Object2DoubleOpenHashMap<>();
// Put a new entry
map.put("foo", 12.50d);
// Access to the entry
double value = map.getDouble("foo");

Object2DoubleOpenHashMapMap的实际实现,它不是线程安全的,但是您仍然可以使用实用方法double put(K k, double v)来使其成为线程安全的,这要归功于装饰器。

创建代码将是:

// Create a thread safe Object2DoubleMap
Object2DoubleMap<String> map =  Object2DoubleMaps.synchronize(
    new Object2DoubleOpenHashMap<>()
);

答案 2 :(得分:10)

其他人已经提出了原始值映射的几个第三方实现。为了完整起见,我想提一些完全摆脱地图的方法你可能想要考虑的。这些解决方案永远不可能实现,但是当它们存在时,它们通常比任何地图都更快,更节省内存。

备选方案1:使用普通旧数组。

一个简单的double[]数组可能不如花哨的地图那样性感,但很少能在紧凑性和访问速度上击败它。

当然,数组有一些限制:它们的大小是固定的(虽然你总是可以创建一个新的数组并将旧的数据复制到其中),它们的键只能是小正整数。为了提高效率,应该相当密集(即使用的密钥总数应该是最高密钥值的一个相当大的部分)。但是,如果您的密钥恰好是这种情况,或者如果您可以安排它,那么原始值数组可能非常有效。

特别是,如果可以为每个键对象分配唯一的小整数ID,则可以将该ID用作数组的索引。类似地,如果您已经将对象存储在数组中(例如,作为一些更复杂的数据结构的一部分)并通过索引查找它们,那么您可以简单地使用相同的索引在另一个中查找任何其他元数据值阵列。

如果您实现了某种碰撞处理机制,您甚至可以免除ID唯一性要求,但此时您正在实施自己的哈希表。在某些情况下可能实际上有意义,但通常在那时它可能更容易使用现有的第三方实现。

备选方案2:自定义对象。

为什么不将这些值保存到对象本身的属性中,而不是将关键对象的映射维护为原始值?毕竟,这就是面向对象编程的全部内容 - 将相关数据分组为有意义的对象。

例如,为什么不给你的点一个布尔HashMap<Point2D, Boolean> onSea属性,而不是维持onSea?当然,您需要为此定义自己的自定义点类,但是如果您愿意,没有理由不能扩展标准Point2D类,所以您可以将自定义点传递给任何需要Point2D的方法。

同样,这种方法可能并不总是直接起作用,例如如果您需要使用无法修改的类(但请参阅下文),或者您要存储的值与多个对象相关联(如ConcurrentHashMap<Point2D, HashMap<Point2D, Double>>中所示)。

但是,对于后一种情况,您仍然可以通过适当地重新设计数据表示来解决问题。例如,您可以定义Map<Node, Map<Node, Double>>类,而不是将加权图表表示为Edge,而不是:

class Edge {
    Node a, b;
    double weight;
}

然后将Edge[](或Vector<Edge>)属性添加到包含连接到该节点的任何边的每个节点。

备选方案3:将多个地图合并为一个。

如果您有多个具有相同键的映射,并且不能像上面建议的那样将值转换为关键对象的新属性,请考虑将它们分组到单个元数据类中,并从键创建单个映射到该对象中。类。例如,考虑定义单个元数据类,而不是Map<Item, Double> accessFrequencyMap<Item, Long> creationTime,而不是:

class ItemMetadata {
    double accessFrequency;
    long creationTime;
}

并且只有一个Map<Item, ItemMetadata>来存储所有元数据值。这比具有多个映射更具内存效率,并且还可以通过避免冗余映射查找来节省时间。

在某些情况下,为方便起见,您可能还希望在每个元数据对象中包含对其对应主对象的引用,以便您可以通过对元数据对象的单个引用来访问它们。这自然而然地分为......

备选方案4:使用装饰器。

作为前两个备选方案的组合,如果您无法直接向关键对象添加额外的元数据属性,请考虑使用可以保存额外值的decorators来包装它们。因此,例如,您可以简单地执行以下操作:

,而不是直接创建具有额外属性的自己的点类
class PointWrapper {
    Point2D point;
    boolean onSea;
    // ...
}

如果你愿意,你甚至可以通过实现方法转发将这个包装器变成一个成熟的装饰器,但即使只是一个简单的“愚蠢的”#34;包装可能足以用于许多目的。

如果您可以安排存储和使用包装器,那么这种方法最有用,这样您就不需要查找与未包装对象相对应的包装器。当然,如果您确实需要偶尔这样做(例如,因为您只是从其他代码接收未包装的对象),那么您可以使用单个Map<Point2D, PointWrapper>来完成此操作,但之后您就可以了有效地回到了之前的替代方案。

答案 3 :(得分:2)

答案 4 :(得分:1)

为了更好地估计这些不同的库如何相互叠加,我整理了一个小基准来检查以下的性能:

  • 300,000次插入的总时间
  • 检查地图中包含1000个样本的包含的平均时间
  • 数据结构的内存大小 我查看了类似Map的结构,其中String为关键字,double为值。选中的框架为Eclipse CollectionHPPCTroveFastUtil,以及HashMapConcurrentHashMap的比较。

简而言之,结果如下:

Filling in 300000 into the JDK HashMap took 107ms
Filling in 300000 into the JDK ConcurrentHashMap took 152ms
Filling in 300000 into the Eclipse map took 107ms
Filling in 300000 into the Trove map took 855ms
Filling in 300000 into the HPPC map took 93ms
Filling in 300000 into the FastUtil map took 163ms
1000 lookups average in JDK HashMap took: 550ns
1000 lookups average in JDK Concurrent HashMap took: 748ns
1000 lookups average in Eclipse Map took: 894ns
1000 lookups average in Trove Map took: 1033ns
1000 lookups average in HPPC Map took: 523ns
1000 lookups average in FastUtil Map took: 680ns
JDK HashMap:            43'809'895B
JDK Concurrent HashMap: 43'653'740B => save  0.36%
Eclipse Map:            35'755'084B => save 18.39%
Trove Map:              32'147'798B => save 26.62%
HPPC Map:               27'366'533B => save 37.53%
FastUtil Map:           31'560'889B => save 27.96%

有关所有细节以及测试应用程序,请查看我的blog entry