写点什么

释放.NET Big Memory 和内存映射文件的能量

  • 2017-08-08
  • 本文字数:4507 字

    阅读完需:约 15 分钟

要点:

  • 通常 Web 服务器具有的内存远远超过了.NET GC 在正常情况下可以有效处理的量。
  • 缓存服务器的性能优势通常会因增加了网络成本而下降。
  • 内存映射文件通常是在系统重新启动后填充缓存的最快方式。
  • 服务器端调优的目的是出站网络连接达到饱和的程度。 通过最小化 CPU、磁盘和内部网络的使用来达到网络连接饱和的程度。
  • 通过在内存中保存对象图,可以获得图形数据库的性能优势,而且不会提高复杂性。

延续关于.NET 平台( part1 part2 )的 Big Memory 主题,本文介绍了使用 Agincore’s Big Memory Pile 在受管 CLR 服务器环境中使用大型数据集的优势。

综述

现今 RAM 已经非常快速和实惠,但是生命周期较短。 每次重新启动进程,内存将被清除,一切都必须重新加载。为了解决这个问题,我们最近添加了内存映射文件来支持我们的解决方案,即 NFX 堆。 使用内存映射文件,可以在重新启动后从磁盘快速获取数据。

总的来说,Big Memory 方法对于开发人员和企业来说是有益的,因为它改变了.NET 平台上的高性能计算模式。 传统的 Big Memory 系统是以 C/C ++ 风格的语言构建的,主要处理字符串和字节数组。 但是当专注于底层数据结构时,就很难解决实际的业务问题了,所以我们要专注于 CLR 对象。 内存堆允许开发人员根据对象实例进行思考,并与具有属性 **、编码、**继承和其他 CLR 原生功能的数亿个实例配合工作

与语言无关的对象模型不同,就像一些软件供应商(如支持 Java 和.NET 的供应商)所提出的那些,它们引入额外的变换以及需要额外流量 / 上下文切换 / 序列化的所有进程外解决方案。相反,我们将讨论进程内的本地堆或类“堆”对象,或者是在托管代码中的大型字节数组中的对象。这些对象对于 GC 来说是不可见的。

用例

为什么有人会使用几十或几百 GB 的 RAM 呢?以下是 Big Memory Pile 技术的一些已验证过的用例。

首先是缓存。在电子商务后端,存储着数十万种用来显示为详细目录列表的产品。每种产品可能有数十种变体。当在单个屏幕上构建一个展示 30 多个产品的目录视图时,即使是单个用户使用渐进式加载滚动页面,也需要很快速地获取到这些对象。为什么不使用 Redis 或 Memcached?因为在同一个进程中做相同的事情,可以节省网络流量和序列化开销。将网络数据包转换成对象可能是非常昂贵的操作过程。如果要持有数十万种产品及其变体,您是否会使用字典 <id,Product>(或 IMemoryCache )?单单缓存数据便提供了足够的动机来使用 RAM,但其实还有更多的方面…

另一个缓存用例是 REST API 服务器,可以将约 5000 万个很少更改的 JSON 向量序列化为 UTF8 编码的字节数组。大约 1024 字节的数组可以直接发送到 Http 流中,使得网络瓶颈处在 80,000 req / sec 左右。

使用复杂对象图是 Pile 的另一个完美案例。在社交应用中,需要遍历 Twitter 上的对话线程。当追踪谁在社区媒体网站上什么时候说了什么时,在内存中持有数亿个小向量的价值是无法估量的。也可以选择使用 graph DB,而在这个案例中,在同一个进程中(它是由 Web MVC 应用程序托管的组件),正是使用了 graph DB。我们现在正在处理每秒 100K 以上的 REST API 调用,这受限于我们的网络连接数,并且我们将 CPU 使用率保持在较低的水平。

在这个和其他用例中,后台工作人员随着变化而异步地更新社交图表。在许多情况下,如前面提到的产品目录,它可被优先完成。但是你不应该使用只持有数据子集的普通缓存来完成更新。

工作原理

Big Memory Pile 通过在大型字符数组中使用 CLR 对象图的透明序列化来解决 GC 问题,有效地“隐藏”了 GC 可获取范围内的对象。然而并不是所有的对象类型都需要完全序列化, 字符串和字节数组对象会被写入堆中,因为缓冲区会绕过所有的序列化机制,在 6 核主机上每秒可完成超过 6 百万次 64 字的字符串插入。

这种方法的主要优点是其实用性。现实生活中的案例,在使用原生 CLR 对象模型时已显示出惊人的整体性能,因为不需要创建专用 DTO,这样可以节省开发时间,而且因为不需要创建中间过程所需的额外副本,运行速度也会 **** 更快

总的来说,Pile 将大部分I/O 绑定代码转换成CPU 限制代码。通常情况下,异步(具有 I/O 绑定)实现的典型案例是100%的同步线性代码,这种代码更加简单,并且在单个服务器上执行多个 100K 操作 / 秒时性能更好,因为 Task 和其他异步 / 等待的实现有隐藏开销(参见这里这里)。

Big Memory 映射文件

内存中的处理过程是快速和容易实现的,但是当进程重新启动时,数据集会丢失,这个数据集通常(几十到几百 GB)是很大的。从原始数据源拉出所有数据可能是非常耗时的,那是在重新启动后无法承受的时间开销。

为了解决这个问题,我们添加了内存映射文件(MMF)来支持使用标准的.NET 类: MemoryMappedFile MemoryMappedViewAccessor 。现在,我们使用 MemoryMappedViewAccessor实例和一些低级技巧来直接使用指针访问数据,而不是使用字节数组作为内存段的后备存储,所有这些操作都使用标准的C#来完成,不需要C ++ 参与,以保持一切简单,特别是构建链。

通过MemoryMappedViewAccessor( MMFMemory 类)写入内存直接修改 OS 层中的虚拟内存页面。如果操作系统不能将这些页面交换到磁盘中,便会尝试将这些页面放在物理 RAM 中。将 Pile 写入 MMF 的一个很好的功能是,进程在关闭后马上重启则不需要从磁盘重新读取所有内容。即使在进程终止之后,操作系统仍将映射到进程地址空间的页面保留。启动时, MMFPile 可以以比从磁盘重新读取更快的方式访问 RAM 中的页面。

请注意,由于在 MMFMemory 类中完成了非托管代码的上下文切换,MMFPile 的性能会比 DefaultPile 慢(基于字节数组)。

以下是一些测试结果:

基准测试插入 200,000,000 字符串 [32] 12 个线程:

(机器:Intel Core I7 3.2 Ghz,6 Core,Win 7 64bit,VS2017,.NET 4.5)

DefaultPile

24 秒 @ 8.3 百万次插入 / 秒 = 8.5 Gb 内存 ; 全 GC <8 ms

MMFPile

  • 41 秒 @ 4.9 百万次插入 / 秒 = 8.5 GB 内存 + 磁盘 ; 全 GC <10 ms
  • 在 Stop()上清除所有数据到磁盘:10 秒
  • 读取所有数据到 ram:48 秒 =〜177 mbyte / 秒

正如你所看到的,MMF 解决方案确实有额外的开销。由于非托管的 MMF 转换,吞吐量较低,并且一旦从磁盘安装 Pile,则耗时与使用磁盘数据预先分配给 RAM 的内存量成比例。然而,你不需要等待加载整个工作集,因为 MMFPile在 Pile.Start() 之后立即可用于写入和读取,数据的全部负载将需要时间开销,在上面的示例中 8.5 GB 数据集需要 48 秒的时间才能在中档 SSD 的 RAM 中预热。

基准测试插入 200,000,000 个 Person**** 对象(具有 7 个字段12 个线程:

DefaultPile

85 秒 @ 2.4 百万次插入 / 秒 = 14.5 Gb 内存 ; 全 GC <10ms

MMFPile

  • 101 秒 @ 1.9 百万次插入 / 秒 = 14.5 GB 内存 + 磁盘 ; 全 GC <10ms
  • 在 Stop() 上清除所有数据到磁盘:30 秒
  • 读取所有数据到 ram:50 秒 =〜290 万字节 / 秒

其他改进

从上一次在 InfoQ 上发表文章以来,我们对 NFX.Pile 做了很多改进:

原始分配器 / 分层设计

Pile 的实现可以更好地分层,从而可以将字符串和字节数组直接从 RAM 的较大的连续块中直接写入 / 读取。整个序列化机制完全绕过字节数组,从而可以使用 Pile 作为一个原始的字节数组分配器。

复制代码
var ptr = pile.Put(“abcdef”); // 这将绕过所有序列化程序
// 并使用 UTF8Encoding 代替
var original = pile.Get(ptr)as string;

性能提升

由于引入了试图避免多线程竞争的滑动窗口优化实现,段分配逻辑已经被修改,并且在多线程插入期间提高了 50%以上的性能。此外,在大多数情况下字符串和字节数组完全绕过串行器会提高速度近 5 百万次插入 / 秒(200%以上的改进)。

枚举

利用 IEnumerable 接口实现,可以获得整个堆的内容。 PileEntry 结构体如下:

复制代码
foreach(var entry in pile){
Console.WriteLine(“{0} points to
{1} bytes”.Args( entry.Pointer,
entry.Size));
var data = pile.Get(entry.Pointer);
}

缓存持久化

出于性能原因,缓存的默认模式是“推测的”。在这种模式下,即使存在足够的内存,散列码冲突也可能导致较低的优先级项从高速缓存中弹出。

缓存服务器可以以“持久”模式存储数据,工作方式更像普通的字典。因为持久化模式需要在 bucket 中进行重新排列,所以相比推测模式会慢 5-10%。对于大多数应用程序来说是难以察觉的。但是需要根据特定的情况进行测试从而确定最佳实现方式。

复制代码
// 为所有表指定 TableOptions,将表持久化
cache.DefaultTableOptions = new TableOptions(“*”)
{
CollisionMode = CollisionMode.Durable
};

地对象突变和预分配

可以在现有 PilePointer 地址改变对象。新的 API Put(PilePointer …)允许在现有位置放置不同的有效载荷。如果新的有效载荷不适合现有的块,那么 Pile 将创建一个内部链接到新位置(* nix 系统中的一个文件系统链接),有效地使原始指针指向新的位置。删除原始指针将删除链接及其所指向的内容。别名是完全透明的,并在读取时产生目标有效载荷。

还可以通过在调用 Put()时指定 preallocateBlockSize,为有效负载预分配更多的 RAM。

复制代码
// 堆中存储链表的实现
public class ListNode{
public PilePointer Previous;
public PilePointer Next;
public PilePointer Value;}
...
private IPile m_Pile;//big memory pile
private PilePointer m_First;//list head
private PilePointer m_Last;//list tail
...
// 将 Person 实例追加到堆中存储的人员链接列表
// 返回最后一个节点
public PilePointer Append(Person person){
var newLast = new ListNode{ Previous = m_Last,
Next = PilePointer.Invalid,
Value = m_Pile.Put(person)};
var existingLast = m_Pile.Get(m_Last);
existingLast.Next = node;
m_Pile.Put(m_Last,existingLast); // 在现有的 ptr m_Last 中进行编辑
m_Last = m_Pile.Put(newLast); // 向尾部添加新节点
return m_Last;
}

更多信息,请参阅我们的视频:.NET Big Memory Object Pile - 在 RAM 中使用数百万个对象

链接

关于作者

Dmitriy Khmaladze在美国的有超过 20 年的 IT 从业经验,主要工作在创业公司和财富 500 强客户。1998 年 Galaxy 在医疗行业主创了 SaaS。在语言与编译设计、分布式架构、系统编程和架构、C / C ++、.NET、Java、Android、IOS、Web 设计、HTML5、CSS、JavaScript、RDBMS 和 NoSQL / NewSQL 等领域有 15 年以上的研究。

查看英文原文: https://www.infoq.com/ articles/Big-Memory-Part-3


感谢冬雨对本文的审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家通过新浪微博( @InfoQ @丁晓昀),微信(微信号: InfoQChina )关注我们。

2017-08-08 17:412930

评论

发布
暂无评论
发现更多内容

Axios拦截器:是前端优化的利器还是不可忽视的安全漏洞源?

测吧(北京)科技有限公司

测试

安全护卫联手:JWT鉴权与Vue路由守卫,确保敏感资源访问权限完全掌控

测吧(北京)科技有限公司

测试

GTC 2024 开幕,英伟达发布新一代 GPU 架构;Apple ID 或将淘汰丨 RTE 开发者日报 Vol.168

声网

架构实战营 - 模块三作业

满心

架构实战营

订阅GPT4之前必须了解的十件事情-【新手向】ChatGPT入门指南

蓉蓉

openai ChatGPT GPT-4

Axios拦截器:优化前后端交互的利器还是纸老虎?

测吧(北京)科技有限公司

测试

助力客户效益增长近10倍!即构宝藏算法是如何做到的?

ZEGO即构

人工智能 AI 算法 直播技术 虚拟背景

从 Linux 内核角度探秘 JDK MappedByteBuffer

bin的技术小屋

jdk RocketMQ JVM Linux内核 java nio

云手机在海外电商中的应用优势

Ogcloud

云手机 海外云手机 云手机海外版 电商云手机

云手机为企业出海提供多元化解决方案

Ogcloud

云手机 海外云手机 云手机海外版 国外云手机 跨境云手机

低代码开发与数据可视化

不在线第一只蜗牛

低代码 数据可视化

看你能解锁哪些新身份?OpenHarmony大使、MVP、金码达人在线申报

OpenHarmony开发者

海外社交营销为什么用云手机?不用普通手机?

Ogcloud

云手机 海外云手机 云手机海外版 国外云手机 跨境云手机

Python如何接收键盘按键

霍格沃兹测试开发学社

使用Python爬取豆瓣电影影评:从数据收集到情感分析

霍格沃兹测试开发学社

Partisia区块链推出MOCCA方案,让资产管理更加可信化且可编程

石头财经

如何快速运用iPaas与协议进行接口对接

谷云科技RestCloud

ipaas 接口对接 协议对接

1688API接口推荐:1688口令转换真实链接接口 审核中

tbapi

1688API接口 1688口令接口 1688淘口令接口

直播预约丨《袋鼠云大数据实操指南》No.1:从理论到实践,离线开发全流程解析

袋鼠云数栈

大数据 离线开发 离线开发离线计算 数据实操

PHP 服务实现监控可观测性最佳实践

观测云

php

CloudIDE就是未来编程的新风向

是但求其发

产品 编程语言 研发效能 企业动态 云端开发

Covalent Network借助大规模的历史Web3数据集,推动人工智能发展

股市老人

Vue路由守卫:是破解安全漏洞的关键还是新的安全风险?

测吧(北京)科技有限公司

测试

Python教程:如何向Word中添加表格

霍格沃兹测试开发学社

Flask蓝图与ORM技术:神奇的组合还是无用功?

测吧(北京)科技有限公司

测试

阿里云 SelectDB 联合 DTS ,一键实现 TP 数据实时入仓

SelectDB

大数据 阿里云 云数据库 数据迁移 #数据库

万界星空科技MES系统在智能生产中的重要作用

万界星空科技

工业互联网 制造业 智能制造 mes 万界星空科技

释放.NET Big Memory和内存映射文件的能量_.NET_Dmitriy Khmaladze_InfoQ精选文章