首页 > 编程语言 >C#对象二进制序列化优化:从位域技术优化压缩数据大小

C#对象二进制序列化优化:从位域技术优化压缩数据大小

时间:2024-01-25 20:13:32浏览次数:31  
标签:set string get C# GPU 序列化 优化 public

目录

 

1. 引言

在操作系统中,进程信息对于系统监控和性能分析至关重要。假设我们需要开发一个监控程序,该程序能够捕获当前操作系统的进程信息,并将其高效地传输到其他端(如服务端或监控端)。在这个过程中,如何将捕获到的进程对象转换为二进制数据,并进行优化,以减小数据包的大小,成为了一个关键问题。本文将通过逐步分析,探讨如何使用位域技术对C#对象进行二进制序列化优化。

操作系统进程信息

首先,我们给出了一个进程对象的字段定义示例。为了通过网络(TCP/UDP)传输该对象,我们需要将其转换为二进制格式。在这个过程中,如何做到最小的数据包大小是一个挑战。

字段名说明示例
PID 进程ID 10565
Name 进程名称 码界工坊
Publisher 发布者 沙漠尽头的狼
CommandLine 命令行 dotnet CodeWF.Tools.dll
CPU CPU(所有内核的总处理利用率) 2.3%
Memory 内存(进程占用的物理内存) 0.1%
Disk 磁盘(所有物理驱动器的总利用率) 0.1 MB/秒
Network 网络(当前主要网络上的网络利用率 0 Mbps
GPU GPU(所有GPU引擎的最高利用率) 2.2%
GPUEngine GPU引擎 GPU 0 - 3D
PowerUsage 电源使用情况(CPU、磁盘和GPU对功耗的影响)
PowerUsageTrend 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响) 非常低
Type 进程类型 应用
Status 进程状态 效率模式

2. 优化过程

2.1. 进程对象定义与初步分析

我们根据字段的示例值确定了每个字段的数据类型。

字段名数据类型说明示例
PID int 进程ID 10565
Name string? 进程名称 码界工坊
Publisher string? 发布者 沙漠尽头的狼
CommandLine string? 命令行 dotnet CodeWF.Tools.dll
CPU string? CPU(所有内核的总处理利用率) 2.3%
Memory string? 内存(进程占用的物理内存) 0.1%
Disk string? 磁盘(所有物理驱动器的总利用率) 0.1 MB/秒
Network string? 网络(当前主要网络上的网络利用率 0 Mbps
GPU string? GPU(所有GPU引擎的最高利用率) 2.2%
GPUEngine string? GPU引擎 GPU 0 - 3D
PowerUsage string? 电源使用情况(CPU、磁盘和GPU对功耗的影响)
PowerUsageTrend string? 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响) 非常低
Type string? 进程类型 应用
Status string? 进程状态 效率模式

创建一个C#类SystemProcess表示进程信息:

  public class SystemProcess
  {
  public int PID { get; set; }
  public string? Name { get; set; }
  public string? Publisher { get; set; }
  public string? CommandLine { get; set; }
  public string? CPU { get; set; }
  public string? Memory { get; set; }
  public string? Disk { get; set; }
  public string? Network { get; set; }
  public string? GPU { get; set; }
  public string? GPUEngine { get; set; }
  public string? PowerUsage { get; set; }
  public string? PowerUsageTrend { get; set; }
  public string? Type { get; set; }
  public string? Status { get; set; }
  }

定义测试数据

  private SystemProcess _codeWFObject = new SystemProcess()
  {
  PID = 10565,
  Name = "码界工坊",
  Publisher = "沙漠尽头的狼",
  CommandLine = "dotnet CodeWF.Tools.dll",
  CPU = "2.3%",
  Memory = "0.1%",
  Disk = "0.1 MB/秒",
  Network = "0 Mbps",
  GPU = "2.2%",
  GPUEngine = "GPU 0 - 3D",
  PowerUsage = "低",
  PowerUsageTrend = "非常低",
  Type = "应用",
  Status = "效率模式"
  };

2.2. 排除Json序列化

将对象转为Json字段串,这在Web开发是最常见的,因为简洁,前后端都方便处理:

  public class SysteProcessUnitTest
  {
  private readonly ITestOutputHelper _testOutputHelper;
   
  private SystemProcess _codeWFObject // 前面已给出定义,这里省
   
  public SysteProcessUnitTest(ITestOutputHelper testOutputHelper)
  {
  _testOutputHelper = testOutputHelper;
  }
   
  /// <summary>
  /// Json序列化大小测试
  /// </summary>
  [Fact]
  public void Test_SerializeJsonData_Success()
  {
  var jsonData = JsonSerializer.Serialize(_codeWFObject);
  _testOutputHelper.WriteLine($"Json长度:{jsonData.Length}");
   
  var jsonDataBytes = Encoding.UTF8.GetBytes(jsonData);
  _testOutputHelper.WriteLine($"json二进制长度:{jsonDataBytes.Length}");
  }
  }
  标准输出: 
  Json长度:366
  json二进制长度:366

尽管Json序列化在Web开发中非常流行,因为它简洁且易于处理,但在TCP/UDP网络传输中,Json序列化可能导致不必要的数据包大小增加。因此,我们排除了Json序列化,并寻找其他更高效的二进制序列化方法。

  {"PID":10565,"Name":"\u7801\u754C\u5DE5\u574A","Publisher":"\u6C99\u6F20\u5C3D\u5934\u7684\u72FC","CommandLine":"dotnet CodeWF.Tools.dll","CPU":"2.3%","Memory":"0.1%","Disk":"0.1 MB/\u79D2","Network":"0 Mbps","GPU":"2.2%","GPUEngine":"GPU 0 - 3D","PowerUsage":"\u4F4E","PowerUsageTrend":"\u975E\u5E38\u4F4E","Type":"\u5E94\u7528","Status":"\u6548\u7387\u6A21\u5F0F"}

2.3. 使用BinaryWriter进行二进制序列化

使用站长前面一篇文章写的二进制序列化帮助类SerializeHelper转换,该类使用BinaryWriter将对象转换为二进制数据。

首先,我们使SystemProcess类实现了一个空接口INetObject,并在类上添加了NetHeadAttribute特性。

  /// <summary>
  /// 网络对象序列化接口
  /// </summary>
  public interface INetObject
  {
  }
  [NetHead(1, 1)]
  public class SystemProcess : INetObject
  {
  // 省略字段定义
  }

然后,我们编写了一个测试方法来验证序列化和反序列化的正确性,并打印了序列化后的二进制数据长度。

  /// <summary>
  /// 二进制序列化测试
  /// </summary>
  [Fact]
  public void Test_SerializeToBytes_Success()
  {
  var buffer = SerializeHelper.SerializeByNative(_codeWFObject, 1);
  _testOutputHelper.WriteLine($"序列化后二进制长度:{buffer.Length}");
   
  var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess>(buffer);
  Assert.Equal("码界工坊", deserializeObj.Name);
  }
  标准输出: 
  序列化后二进制长度:152

比Json体积小了一半多(366到152),上面单元测试也测试了数据反序列化后验证数据是否正确,我们就以这个基础继续优化。

2.4. 数据类型调整

为了进一步优化二进制数据的大小,我们对数据类型进行了调整。通过对进程数据示例的分析,我们发现一些字段的数据类型可以更加紧凑地表示。例如,CPU利用率可以只传递数字部分(如2.3),而不需要传递百分号。这种调整可以减小数据包的大小。

字段名数据类型说明示例
PID int 进程ID 10565
Name string? 进程名称 码界工坊
Publisher string? 发布者 沙漠尽头的狼
CommandLine string? 命令行 dotnet CodeWF.Tools.dll
CPU float CPU(所有内核的总处理利用率) 2.3
Memory float 内存(进程占用的物理内存) 0.1
Disk float 磁盘(所有物理驱动器的总利用率) 0.1
Network float 网络(当前主要网络上的网络利用率 0
GPU float GPU(所有GPU引擎的最高利用率) 2.2
GPUEngine byte GPU引擎,0:无,1:GPU 0 - 3D 1
PowerUsage byte 电源使用情况(CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 1
PowerUsageTrend byte 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 0
Type byte 进程类型,0:应用,1:后台进程 0
Status byte 进程状态,0:正常运行,1:效率模式,2:挂起 1

修改测试数据定义:

  [NetHead(1, 2)]
  public class SystemProcess2 : INetObject
  {
  public int PID { get; set; }
  public string? Name { get; set; }
  public string? Publisher { get; set; }
  public string? CommandLine { get; set; }
  public float CPU { get; set; }
  public float Memory { get; set; }
  public float Disk { get; set; }
  public float Network { get; set; }
  public float GPU { get; set; }
  public byte GPUEngine { get; set; }
  public byte PowerUsage { get; set; }
  public byte PowerUsageTrend { get; set; }
  public byte Type { get; set; }
  public byte Status { get; set; }
  }
  /// <summary>
  /// 普通优化字段数据类型
  /// </summary>
  private SystemProcess2 _codeWFObject2 = new SystemProcess2()
  {
  PID = 10565,
  Name = "码界工坊",
  Publisher = "沙漠尽头的狼",
  CommandLine = "dotnet CodeWF.Tools.dll",
  CPU = 2.3f,
  Memory = 0.1f,
  Disk = 0.1f,
  Network = 0,
  GPU = 2.2f,
  GPUEngine = 1,
  PowerUsage = 1,
  PowerUsageTrend = 0,
  Type = 0,
  Status = 1
  };

添加单元测试如下:

  /// <summary>
  /// 二进制序列化测试
  /// </summary>
  [Fact]
  public void Test_SerializeToBytes2_Success()
  {
  var buffer = SerializeHelper.SerializeByNative(_codeWFObject2, 1);
  _testOutputHelper.WriteLine($"序列化后二进制长度:{buffer.Length}");
   
  var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess2>(buffer);
  Assert.Equal("码界工坊", deserializeObj.Name);
  Assert.Equal(2.2f, deserializeObj.GPU);
  }

测试结果:

  标准输出: 
  序列化后二进制长度:99

又优化了50%左右(152到99),爽不爽?继续,还有更爽的。

2.5. 再次数据类型调整与位域优化

更进一步地,我们引入了位域技术。位域允许我们更加精细地控制字段在内存中的布局,从而进一步减小二进制数据的大小。我们重新定义了字段规则,并使用位域来表示一些枚举值字段。通过这种方式,我们能够显著地减小数据包的大小。

看前面一张表,部分字段只是一些枚举值,使用的byte表示,即8位(bit),其中比如进程类型只有2个状态(0:应用,1:后台进程),正好可以用1位即表示;像电源使用情况,无非就是5个状态,用3位可表示全,按这个规则我们重新定义字段规则如下:

字段名数据类型说明示例
PID int 进程ID 10565
Name string? 进程名称 码界工坊
Publisher string? 发布者 沙漠尽头的狼
CommandLine string? 命令行 dotnet CodeWF.Tools.dll
Data byte[8] 固定大小的几个字段,见下表定义  

固定字段(Data)的详细说明如下:

字段名OffsetSize说明示例
CPU 0 10 CPU(所有内核的总处理利用率),最后一位表示小数位,比如23表示2.3% 23
Memory 10 10 内存(进程占用的物理内存),最后一位表示小数位,比如1表示0.1%,值可根据基本信息计算 1
Disk 20 10 磁盘(所有物理驱动器的总利用率),最后一位表示小数位,比如1表示0.1%,值可根据基本信息计算 1
Network 30 10 网络(当前主要网络上的网络利用率),最后一位表示小数位,比如253表示25.3%,值可根据基本信息计算 0
GPU 40 10 GPU(所有GPU引擎的最高利用率),最后一位表示小数位,比如253表示25.3 22
GPUEngine 50 1 GPU引擎,0:无,1:GPU 0 - 3D 1
PowerUsage 51 3 电源使用情况(CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 1
PowerUsageTrend 54 3 电源使用情况趋势(一段时间内CPU、磁盘和GPU对功耗的影响),0:非常低,1:低,2:中,3:高,4:非常高 0
Type 57 1 进程类型,0:应用,1:后台进程 0
Status 58 2 进程状态,0:正常运行,1:效率模式,2:挂起 1

上面这张表是位域规则表,Offset表示字段在Data字节数组中的位置(以bit为单位计算),Size表示字段在Data中占有的大小(同样以bit单位计算),如Memory字段,在Data字节数组中,占据10到20位的空间。

修改类定义如下,注意看代码中的注释:

  [NetHead(1, 3)]
  public class SystemProcess3 : INetObject
  {
  public int PID { get; set; }
  public string? Name { get; set; }
  public string? Publisher { get; set; }
  public string? CommandLine { get; set; }
  private byte[]? _data;
  /// <summary>
  /// 序列化,这是实际需要序列化的数据
  /// </summary>
  public byte[]? Data
  {
  get => _data;
  set
  {
  _data = value;
   
  // 这是关键:在反序列化将byte转换为对象,方便程序中使用
  _processData = _data?.ToFieldObject<SystemProcessData>();
  }
  }
   
  private SystemProcessData? _processData;
   
  /// <summary>
  /// 进程数据,添加NetIgnoreMember在序列化会忽略
  /// </summary>
  [NetIgnoreMember]
  public SystemProcessData? ProcessData
  {
  get => _processData;
  set
  {
  _processData = value;
   
  // 这里关键:将对象转换为位域
  _data = _processData?.FieldObjectBuffer();
  }
  }
  }
   
  public record SystemProcessData
  {
  [NetFieldOffset(0, 10)] public short CPU { get; set; }
  [NetFieldOffset(10, 10)] public short Memory { get; set; }
  [NetFieldOffset(20, 10)] public short Disk { get; set; }
  [NetFieldOffset(30, 10)] public short Network { get; set; }
  [NetFieldOffset(40, 10)] public short GPU { get; set; }
  [NetFieldOffset(50, 1)] public byte GPUEngine { get; set; }
  [NetFieldOffset(51, 3)] public byte PowerUsage { get; set; }
  [NetFieldOffset(54, 3)] public byte PowerUsageTrend { get; set; }
  [NetFieldOffset(57, 1)] public byte Type { get; set; }
  [NetFieldOffset(58, 2)] public byte Status { get; set; }
  }

添加单元测试如下:

  /// <summary>
  /// 极限优化字段数据类型
  /// </summary>
  private SystemProcess3 _codeWFObject3 = new SystemProcess3()
  {
  PID = 10565,
  Name = "码界工坊",
  Publisher = "沙漠尽头的狼",
  CommandLine = "dotnet CodeWF.Tools.dll",
  ProcessData = new SystemProcessData()
  {
  CPU = 23,
  Memory = 1,
  Disk = 1,
  Network = 0,
  GPU = 22,
  GPUEngine = 1,
  PowerUsage = 1,
  PowerUsageTrend = 0,
  Type = 0,
  Status = 1
  }
  };
   
  /// <summary>
  /// 二进制极限序列化测试
  /// </summary>
  [Fact]
  public void Test_SerializeToBytes3_Success()
  {
  var buffer = SerializeHelper.SerializeByNative(_codeWFObject3, 1);
  _testOutputHelper.WriteLine($"序列化后二进制长度:{buffer.Length}");
   
  var deserializeObj = SerializeHelper.DeserializeByNative<SystemProcess3>(buffer);
  Assert.Equal("码界工坊", deserializeObj.Name);
  Assert.Equal(23, deserializeObj.ProcessData.CPU);
  Assert.Equal(1, deserializeObj.ProcessData.PowerUsage);
  }

测试输出:

  标准输出: 
  序列化后二进制长度:86

99又优化到86个字节,13个字节哦,有极限网络环境下非常可观,比如100万数据,那不就是12.4MB了?关于位域序列化和反序列的代码这里不细说了,很枯燥,站长可能也说不清楚,代码长这样:

  public partial class SerializeHelper
  {
  public static byte[] FieldObjectBuffer<T>(this T obj) where T : class
  {
  var properties = typeof(T).GetProperties();
  var totalSize = 0;
   
  // 计算总的bit长度
  foreach (var property in properties)
  {
  if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
  {
  continue;
  }
   
  var offsetAttribute =
  (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
  totalSize = Math.Max(totalSize, offsetAttribute.Offset + offsetAttribute.Size);
  }
   
  var bufferLength = (int)Math.Ceiling((double)totalSize / 8);
  var buffer = new byte[bufferLength];
   
  foreach (var property in properties)
  {
  if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
  {
  continue;
  }
   
  var offsetAttribute =
  (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
  dynamic value = property.GetValue(obj)!; // 使用dynamic类型动态获取属性值
  SetBitValue(ref buffer, value, offsetAttribute.Offset, offsetAttribute.Size);
  }
   
  return buffer;
  }
   
  public static T ToFieldObject<T>(this byte[] buffer) where T : class, new()
  {
  var obj = new T();
  var properties = typeof(T).GetProperties();
   
  foreach (var property in properties)
  {
  if (!Attribute.IsDefined(property, typeof(NetFieldOffsetAttribute)))
  {
  continue;
  }
   
  var offsetAttribute =
  (NetFieldOffsetAttribute)property.GetCustomAttribute(typeof(NetFieldOffsetAttribute))!;
  dynamic value = GetValueFromBit(buffer, offsetAttribute.Offset, offsetAttribute.Size,
  property.PropertyType);
  property.SetValue(obj, value);
  }
   
  return obj;
  }
   
  /// <summary>
  /// 将值按位写入buffer
  /// </summary>
  /// <param name="buffer"></param>
  /// <param name="value"></param>
  /// <param name="offset"></param>
  /// <param name="size"></param>
  private static void SetBitValue(ref byte[] buffer, int value, int offset, int size)
  {
  var mask = (1 << size) - 1;
  buffer[offset / 8] |= (byte)((value & mask) << (offset % 8));
  if (offset % 8 + size > 8)
  {
  buffer[offset / 8 + 1] |= (byte)((value & mask) >> (8 - offset % 8));
  }
  }
   
  /// <summary>
  /// 从buffer中按位读取值
  /// </summary>
  /// <param name="buffer"></param>
  /// <param name="offset"></param>
  /// <param name="size"></param>
  /// <param name="propertyType"></param>
  /// <returns></returns>
  private static dynamic GetValueFromBit(byte[] buffer, int offset, int size, Type propertyType)
  {
  var mask = (1 << size) - 1;
  var bitValue = (buffer[offset / 8] >> (offset % 8)) & mask;
  if (offset % 8 + size > 8)
  {
  bitValue |= (buffer[offset / 8 + 1] << (8 - offset % 8)) & mask;
  }
   
  dynamic result = Convert.ChangeType(bitValue, propertyType); // 根据属性类型进行转换
  return result;
  }
  }

3. 优化效果与总结

通过逐步优化,我们从最初的Json序列化366字节减小到了使用普通二进制序列化的152字节,再进一步使用位域技术优化到了86字节。这种优化在网络传输中是非常可观的,尤其是在需要传输大量数据的情况下。

本文通过一个示例案例,探讨了C#对象二进制序列化的优化方法。通过使用位域技术,我们实现了对数据包大小的极限压缩,提高了网络传输的效率。这对于开发C/S程序来说是一种乐趣,也是追求极致性能的一种体现。

最后,我们提供了本文测试源码的Github链接,供读者参考和学习。

彩蛋:该仓库有上篇《C#百万对象序列化深度剖析:如何在网络传输中实现速度与体积的完美平衡 (dotnet9.com)》案例代码,也附带了TCP、UDP服务端与客户端联调测试程序哦。

 

【出处】:https://www.cnblogs.com/Dotnet9-com/p/17981055

=======================================================================================

评论区(节选)

#3楼 2024-01-23 09:38 | eveloper

你这思路就不对,要优化直接编码完上压缩
你这二进制,不仅查问题难,升级也难,兼容也难,可读性也差

---------------------------------------------------------------------------------------

#4楼 2024-01-23 13:55 | a1010

用MessagePack 试了一下,和作者完全一样的类型定义和实例化复制,二进制后长度是70字节.用的是.net8+MessagePack:2.6.100-alpha 

---------------------------------------------------------------------------------------

#9楼 2024-01-23 16:43 | firstrose

@eveloper
你的思路不对。压缩的成本很高,对于二进制数据压缩率未必高到哪里去。
而且最重要的是,压缩数据的鲁棒性很差,你得额外加校验数据。如果要能恢复损坏的部分,那还要再加冗余。不想加冗余可以,传错一位就重传吧。

---------------------------------------------------------------------------------------

#10楼 2024-01-24 09:48 | a1010


从压缩效率上来讲还是messagepack比较占优,memorypack主要还是速度上比较快

环境:
.NET8
MemoryPack=1.10.0
MessagePack=2.6.100-alpha

---------------------------------------------------------------------------------------

#11楼 2024-01-24 17:39 | 数据的流

2.5既然进行字段位域优化,为什么前边pid,name等几个字段不进行一块优化呢,之前在一款客户端游戏中见到过这种极致序列化的路子

 

在评论区看看,很多人给出了不同的意见,感兴趣的自己去原文评论区查看吧

标签:set,string,get,C#,GPU,序列化,优化,public
From: https://www.cnblogs.com/mq0036/p/17988053

相关文章

  • C# AVEVA MARINE DRAWING TREE VIEW 快速读取方法,速度真的很快
    一般来讲我们使用MARAPI里面的ElementChildFirstGet和ElementSiblingNextGet函数去遍历而获得图元'''<summary>'''获取当前视图的全部的子视图的句柄'''</summary>'''<paramname="draftApp">M......
  • 【ChatGPT 和文心一言哪个更好用?】
    ChatGPT和文心一言哪个更好用?随着人工智能技术的飞速发展,AI助手已经成为人们日常生活和工作中不可或缺的一部分。在众多AI助手中,ChatGPT和文心一言备受关注。本文将从智能回复、语言准确性、知识库丰富度等方面对这两大AI助手进行比较,以探讨它们在不同方面的优势和劣势。......
  • Semantic Kernel 通过 LocalAI 集成本地模型
    本文是基于LLama2是由Meta开源的大语言模型,通过LocalAI来集成LLama2来演示Semantickernel(简称SK)和本地大模型的集成示例。SK可以支持各种大模型,在官方示例中多是OpenAI和AzureOpenAIservice的GPT3.5+。今天我们就来看一看如何把SK和本地部署的开源大模型集成起来......
  • gcc
    #-Wl,-z,noexecstack是一个选项,用于告诉链接器不在程序的栈上执行代码。这个选项可以提高程序的安全性,因为它可以防止栈溢出攻击。栈溢出攻击是一种常见的攻击方式,攻击者通过向程序的栈中注入恶意代码来控制程序的执行流程。使用-Wl,-z,noexecstack选项可以防止这种攻击方式......
  • ABC337G Tree Inversion
    思路对于每个\(1\lei\len\)的\(i\)都要求答案,那我们考虑dp,去思考如何转移\(f_i\)。先不考虑全局,只考虑子树内的贡献,设\(g_u\)表示以\(u\)为根的子树内,对\(u\)来说满足条件的点对数。对于\(u\)的儿子\(v\),对\(v\)来说合法那么对\(u\)来说也一定合法。因为......
  • 基于CefSharp、WPF开发浏览器项目----系列文章
    基于CefSharp、WPF开发浏览器项目基于CefSharp开发浏览器(十一)增添F11、F12功能基于CefSharp开发浏览器(十)CefSharp.Wpf中文输入法偏移处理基于CefSharp开发浏览器(九)浏览器历史记录弹窗面板基于CefSharp开发浏览器(八)浏览器收藏夹栏基于CefSharp开发浏览器(七)浏览器收藏夹菜单基......
  • Legacy (线段树优化建图)
    题目链接:Legacy-洛谷|计算机科学教育新生态(luogu.com.cn)题解:考虑题目中一个点向区间连边,如真的对区间中的每一点分别连边后跑最短路,时间空间都要炸。因为是一个点向区间连边,考虑线段树。1到n构造两颗区间线段数 观察上图(从网上搬的)两颗线段树,一颗入树父亲向儿子连......
  • 2020-2021 ICPC Southwestern European Regional Contest (SWERC 2020)
    Preface经典前期天胡开局,70min的时候就已经过6题+会另外3题了,本来以为又要像昨天那样提前下班了后面好家伙FGH连着卡,最后磕磕碰碰刚好在结束前写完10个题,强行续上班时长了属于是A.Gratitude签到,注意出现次数相同的字符串的处理#include<cstdio>#include<iostream>#includ......
  • Golang gRPC概述及入门示例
    1、概述1.1什么是gRPCRPC的全称是RemoteProcedureCall,远程过程调用。RPC是一种协议,它实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。而gRPC又是什么呢?用官方的话来说:A......
  • .NET GC的SustainedLowLatency模式引发内存的问题
    最近遇到一个问题,应用的内存占用升上去后一直降不下来,打了dump文件后发现GC的Generation0上有很多空白区间没释放,按道理第0代堆是经常回收的,怎么会有那么多空白区间呢?查阅了相关文档后,发现这是由代码中的System.Runtime.GCSettings.LatencyMode=System.Runtime.GCLatencyMode......