如何在F#中实现高效的字符串池化?

4
在 F# 中实现一个自定义字符串类型来对字符串进行interning。我需要将大型 CSV 文件读入内存。鉴于大多数列是分类的,值是重复的,因此首次遇到该值时创建新字符串并仅在后续出现时引用它以节省内存是有意义的。
在 C# 中,我通过创建全局 intern 池 (concurrent dict) 并在设置值之前查找字典是否已经存在来实现这一点。如果存在,只需指向已经在字典中的字符串。如果没有,将其添加到字典中,并将值设置为刚刚添加到字典中的字符串。
对于新手而言,不知道在 F# 中做这个最好的方法是什么。将使用记录命名元组等中的新字符串类型,并且它必须与并发进程一起工作。
编辑: String.Intern 使用 Intern Pool。我的理解是,对于大型池,它不是非常高效,并且不会被垃圾回收,即任何/所有 interned 字符串都将在应用程序的生命周期内保留在 intern pool 中。想象一个应用程序,在其中你读取一个文件,执行一些操作并写入数据。使用 Intern Pool 解决方案可能有效。现在想象你要做同样的事情 100 次,每个文件中的字符串都很少相同。如果在堆上分配内存,在处理每个文件后,我们可以强制垃圾收集器清除不必要的字符串。
我应该提到,我实际上无法找出如何在 F# 中执行 C# 方法(除了实现 C# 类型并在 F# 中使用它)。
缓存模式与我所寻找的略有不同吗?我们不是缓存计算结果——我们确保每个字符串对象最多只创建一次,并且所有后续创建的相同字符串只是对原始字符串的引用。使用字典来完成这一点是一种方法,使用 String.Intern 是另一种方法。
如果我漏掉了什么显而易见的东西,请原谅。

6
和在 C# 中一样做有什么问题吗? - Fyodor Soikin
是的,C# 的方法是正确的。在 F# 中,这被称为 memoize。 - Ray
2
为什么不使用String.Intern,而要自己编写代码呢? - kvb
1个回答

1

我有几件事要说,所以我会把它们作为答案发布。

首先,我猜想 String.Intern 在 F# 中和在 C# 中一样有效。

let x = "abc"
let y = StringBuilder("a").Append("bc").ToString()
printfn "1 : %A" (LanguagePrimitives.PhysicalEquality x y) // false
let y2 = String.Intern y
printfn "2 : %A" (LanguagePrimitives.PhysicalEquality x y2) // true

第二,你在C#解决方案中是否与字典一起使用了String.Intern?如果是,为什么不在从文件输入后将字符串准备好后只需执行s = String.Intern(s)?
为您的业务域创建用于处理字符串去重的类型是非常糟糕的想法。您不希望您的业务域受到这种低级别的污染。
至于自己编写代码。我几年前就这样做了,可能是为了避免您提到的字符串无法被垃圾回收的问题,但我从未测试过那是否真的是个问题。
对于每个列(或列类型)可能会重复相同值的情况,使用字典(或其他内容)可能是个好主意。(这基本上就是您已经说过的。)
只有在读取文件信息并将其放入内部数据结构时才有必要保持这些字典处于活动状态。您可能认为需要这些字典进行后续读取,但我对此并不确定。
重要的是去除大部分字符串的重复,而不一定是每一个重复的字符串。因此,您可以按照所示简化解决方案。过度复杂化解决方案以挤出最后一点内存节省可能并没有多大收益。
  • 在文件被读取和结构填充之后释放字典将具有优势,因为当字符串不再真正需要时,不会继续保留它们。当然,通过不保留字典,您也可以节省内存。
  • 我认为在这里实现中没有处理并发问题的必要。String.Intern 必须必然免疫并发问题。如果您使用建议的设计自己编写,您将不会同时使用它。每个被读取的文件都将有其自己的列字典集。


    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接