cis*_*eat 27 memory functional-programming immutability
假设我们有一个类似于内存的类Image
,有可链接的方法,比如Resize()
和ConvertTo()
.
如果这个类是不可变的,那么当我开始做类似的事情时,它会不会占用大量的内存i.Resize(500, 800).Rotate(90).ConvertTo(Gif)
,相比之下,一个可变的自我修改?如何用函数式语言处理这种情况?
Nor*_*sey 24
如果这个类是不可变的,它不会占用大量的内存吗?
通常,您对该单个对象的内存要求可能会翻倍,因为您可能会同时拥有"旧副本"和"新副本".因此,您可以在程序的生命周期中查看此现象,因为分配的大对象比在典型的命令性程序中分配的大一个.(没有"工作"的对象只是坐在那里,具有与任何其他语言相同的内存要求.)
如何用函数式语言处理这种情况?
什么都不做. 或者更准确地说,分配健康状况良好的新对象.如果您正在使用为函数式编程设计的实现,那么分配器和垃圾收集器几乎肯定会针对高分配率进行调整,一切都会好的.如果您不幸尝试在JVM上运行功能代码,那么性能将不如定制实现那么好,但对于大多数程序来说,它仍然会很好.
你能提供更多细节吗?
当然.我将采用一个非常简单的例子:1000x1000灰度图像,每像素8位,旋转180度.这就是我们所知道的:
在内存中表示图像需要1MB.
如果图像是可变的,则可以通过进行更新来旋转180度.所需的临时空间量足以容纳一个像素.你编写了一个双重嵌套循环
for (i in columns) do
for (j in first half of rows) do {
pixel temp := a[i, j];
a[i, j] := a[width-i, height-j];
a[width-i, height-j] := tmp
}
Run Code Online (Sandbox Code Playgroud)如果图像是不可变的,则需要创建一个完整的新图像,暂时您必须挂起旧图像.代码是这样的:
new_a = Image.tabulate (width, height) (\ x y -> a[width-x, height-y])
Run Code Online (Sandbox Code Playgroud)
该tabulate
函数分配一个完整的,不可变的2D数组并初始化其内容.在此操作期间,旧图像暂时占用内存.但是当tabulate
完成时,a
不再使用旧图像,并且其内存现在是免费的(也就是说,有资格由垃圾收集器回收).然后,所需的临时空间量足以容纳一个图像.
轮换正在进行时,不需要复制其他类的对象; 只有旋转的图像才需要临时空间.
NB对于其他操作,诸如重新缩放或旋转90度(非正方形)图像,这是很可能的是,即使当图像是可变的,则整个图像的临时副本将是必要的,因为尺寸变化.另一方面,可以使用具有非常小的临时空间的突变来逐个像素地完成颜色空间变换和其他计算.
Dan*_*ory 11
是.不可变性是计算中永恒时空权衡的一个组成部分:您牺牲内存来换取上述锁和其他并发访问控制措施所带来的并行性提高的处理速度.
功能语言通常通过将它们分成非常精细的颗粒来处理这种性质的操作.您的Image类实际上并不保存图像的逻辑数据位; 相反,它使用指针或引用包含图像数据的更小的不可变数据段.当操作需要对图像数据进行的,较小的片段克隆和突变,和图像的新副本更新为引用返回 - 这点尚未被复制或改变,一直保持完整的数据大部分.
这就是功能设计需要与命令式设计不同的基本思维过程的一个原因.算法本身不仅布局不同,而且数据存储和结构也需要以不同的方式布局,以解决复制的内存开销问题.