2024年7月

天下武功,无坚不破,唯快不破!

要想赢得程序员的欢心,工具的速度至关重要。仅需这一优势,即可使其在众多竞争对手中脱颖而出,迅速赢得开发者的偏爱。以这款号称下一代极速 Python 包管理工具——uv 为例,它的核心竞争力在于「快」和「丝滑替代」。自年初开源以来,不到半年便实现了 Star 数破万的壮举,犹如一匹黑马,闯出了自己的一片天地。

上周备受关注的开源项目包括:为老款 Mac 带来新生命的 OpenCore-Legacy-Patcher,采用 Rust 开发的开源 Flash 播放器模拟器 Ruffle,以及免费的 LaTeX 在线编辑器 Overleaf。它们各具特色,让我们一同来了解一下吧!

  • 本文目录
    • 1. 开源热搜项目
      • 1.1 极快的 Python 包管理工具:uv
      • 1.2 为老款 Mac 注入新的活力:OpenCore-Legacy-Patcher
      • 1.3 LaTeX 在线编辑器:Overleaf
      • 1.4 开源的 Flash Player 模拟器:Ruffle
      • 1.5 美化你的 GitHub 个人首页:beautify-github-profile
    • 2. HelloGitHub 热评
      • 2.1 让你上瘾的英语学习工具:Earthworm
      • 2.2 免费的开源视频转码工具:HandBrake
    • 3. 结尾

1. 开源热搜项目

1.1 极快的 Python 包管理工具:uv

主语言:Rust

Star:14k

周增长:1.3k

该项目是基于 Rust 开发的下一代 Python 包管理工具,可用于替代传统的 Python 包和环境管理工具。它兼容 pip、pip-tools 和 virtualenv 命令,速度比它们快 10-100 倍,并利用全局依赖缓存更加节省硬盘空间,开箱即用支持 Windows、Linux 和 macOS 系统。

GitHub 地址→
github.com/astral-sh/uv

1.2 为老款 Mac 注入新的活力:OpenCore-Legacy-Patcher

主语言:Python

Star:11k

周增长:100

这个用 Python 写的工具是专为 Apple 不再支持的老款 Mac 设备,提供升级系统的能力。它支持 2007 年以后生产的老旧 Mac 电脑,升级系统到 Big Sur、Monterey、Ventura 和 Sonoma 版本,并解锁 AirDrop、副屏(Sidecar)和夜览等功能。

GitHub 地址→
github.com/dortania/OpenCore-Legacy-Patcher

1.3 LaTeX 在线编辑器:Overleaf

主语言:JavaScript

Star:13k

周增长:300

这是一个开源的在线实时协作 LaTeX 编辑器,支持同步编辑、多种免费模板、参考管理、评论和聊天等功能。它提供了社区版和企业版,社区版适合个人使用,相当于一个免费的 LaTeX 在线编辑器,企业版则是一款更强大、支持多人协作的 LaTeX 编辑器。

GitHub 地址→
github.com/overleaf/overleaf

1.4 开源的 Flash Player 模拟器:Ruffle

主语言:Rust

Star:14k

周增长:200

这是一个用 Rust 编写的 Adobe Flash Player 模拟器,支持网站嵌入脚本或在本地桌面播放 Flash 动画和游戏。它开箱即用、安装简单,利用 Rust 和 WebAssembly 技术,解决了原本 Flash Player 存在的各种内存安全问题。尽管 Flash 已经成为历史,但其中仍有许多好玩的动画和游戏,不应该被遗忘。

GitHub 地址→
github.com/ruffle-rs/ruffle

1.5 美化你的 GitHub 个人首页:beautify-github-profile

主语言:Other

Star:10k

周增长:400

该项目收集了一系列美化 GitHub 个人首页的工具和教程,包括徽章、小部件和各种个性化的元素,打造个性化且美观的 GitHub 个人首页。

GitHub 地址→
github.com/rzashakeri/beautify-github-profile

2. HelloGitHub 热评

在这个章节,将会分享下本周 HelloGitHub 网站上的热门开源项目,欢迎与我们分享你上手这些开源项目后的使用体验。

2.1 让你上瘾的英语学习工具:Earthworm

主语言:JavaScript

这是一个开源的在线学习英语网站,支持自托管和本地运行。它采用连词成句、循序渐进的方法帮你学习英语。通过不断地重复形成肌肉记忆,并结合游戏奖励和积分排名的方式,让背单词变得有趣且高效。

项目详情→
hellogithub.com/repository/9433615761f548cf9648434c670cd85b

2.2 免费的开源视频转码工具:HandBrake

主语言:C

这是一款功能强大、社区驱动的开源视频转码工具,它支持将各种不同格式的视频文件,转换为 MP4 和 MKV 等常见格式。

项目详情→
hellogithub.com/repository/f18be7647c84492e943adb0c965b1b59

3. 结尾

以上就是本期「GitHub 热点速览」的全部内容,希望你能够在这里找到自己感兴趣的开源项目,如果你有其他好玩、有趣的 GitHub 开源项目想要分享,欢迎来
HelloGitHub
与我们交流和讨论。

往期回顾

在C#中使用RabbitMQ做个简单的发送邮件小项目

前言

好久没有做项目了,这次做一个发送邮件的小项目。发邮件是一个比较耗时的操作,之前在我的个人博客里面回复评论和友链申请是会通过发送邮件来通知对方的,不过当时只是简单的进行了异步操作。
那么这次来使用RabbitMQ去统一发送邮件,我的想法是通过调用邮件发送接口,将请求发送到队列。然后在队列中接收并执行邮件发送操作。
本文采用简单的点对点模式:

在点对点模式中,只会有一个消费者进行消费。

对于常用的RabbitMQ队列模式不了解的可以查看往期文章:

架构图

image

简单描述下项目结构。项目主要分为生产者、RabbitMQ、消费者这3个对象。

  • 生产者(Publisher)
    :负责将邮件发送请求发送到RabbitMQ的队列中。
  • RabbitMQ服务器
    :作为消息中间件,用于接收并存储生产者发送的消息。
  • 消费者(Consumer)
    :从RabbitMQ的队列中接收邮件发送请求,并执行实际的邮件发送操作。

项目结构

  • RabbitMQEmailProject
    • EamilApiProject 生产者
      • Controllers 控制器
      • Service 服务
    • RabiitMQClient 消费者
      • Program 主程序
    • Model 实体类

开始编码(一阶段)

首先我们先简单的将生产者和消费者代码完成,让生产者能够发送消息,消费者能够接受并处理消息。代码有点多,不过注释也多很容易看懂。
给生产者和消费者都安装上用于处理RabiitMQ连接的Nuget包:

dotnet add package RabbitMQ.Client

生产者

EamilApiProject

配置文件

appsetting.json

"RabbitMQ": {  
  "Hostname": "localhost",  
  "Port": "5672",  
  "Username": "guest",  
  "Password": "guest"  
}

控制器

[ApiController]  
[Route("[controller]")]  
public class SendEmailController : ControllerBase  
{  
    private readonly EmailService _emailService;  
  
    public SendEmailController(EmailService emailService)  
    {       
	     _emailService = emailService;  
    }  
    [HttpPost(Name = "SendEmail")]  
    public IActionResult Post([FromBody] EmailDto emailRequest)  
    {        
	    _emailService.SendEamil(emailRequest);  
        return Ok("邮件已发送");  
    }
}

服务

RabbitMQ连接服务

public class RabbitMqConnectionFactory :IDisposable  
{  
    private readonly RabbitMqSettings _settings;  
    private IConnection _connection;  
  
    public RabbitMqConnectionFactory (IOptions<RabbitMqSettings> settings)  
    {       
	     _settings = settings.Value;  
    }  
    public IModel CreateChannel()  
    {        
    if (_connection == null || _connection.IsOpen == false)  
        {            
        var factory = new ConnectionFactory()  
            {  
                HostName = _settings.Hostname,  
                UserName = _settings.Username,  
                Password = _settings.Password  
            };  
            _connection = factory.CreateConnection();  
        }  
        return _connection.CreateModel();  
    }  
    public void Dispose()  
    {        
	    if (_connection != null)  
        {            
	        if (_connection.IsOpen)  
            {               
	             _connection.Close();  
            }            
            _connection.Dispose();  
        }    
    }
}

发送邮件服务

public class EmailService
{
    private readonly RabbitMqConnectionFactory _connectionFactory;

    public EmailService(RabbitMqConnectionFactory connectionFactory)
    {
        _connectionFactory = connectionFactory;
    }
    public void SendEamil(EmailDto emailDto)
    {
        using var channel = _connectionFactory.CreateChannel();
        var properties = channel.CreateBasicProperties();
        properties.Persistent = true;//消息持久化
        
        var message = JsonConvert.SerializeObject(emailDto);
        var body = Encoding.UTF8.GetBytes(message);

        channel.BasicPublish( string.Empty, "email_queue", properties, body);
    }
}

注册服务

builder.Services.Configure<RabbitMqSettings>(builder.Configuration.GetSection("RabbitMQ"));
builder.Services.AddSingleton<RabbitMqConnectionFactory >();
builder.Services.AddTransient<EmailService>();

实体

Model

public class EmailDto  
{  
    /// <summary>  
    /// 邮箱地址  
    /// </summary>  
    public string Email { get; set; }  
    /// <summary>  
    /// 主题  
    /// </summary>  
    public string Subject { get; set; }  
    /// <summary>  
    /// 内容  
    /// </summary>  
    public string Body { get; set; }  
}
public class RabbitMqSettings  
{  
    public string Hostname { get; set; }  
    public string Port { get; set; }  
    public string Username { get; set; }  
    public string Password { get; set; }  
}

消费者

RabiitMQClient

static void Main(string[] args)  
{  
    var factory = new ConnectionFactory { HostName = "localhost", Port = 5672, UserName = "guest", Password = "guest" };  
    using var connection = factory.CreateConnection();  
    using var channel = connection.CreateModel();  
  
    channel.QueueDeclare(queue: "email_queue",  
        durable: true,//是否持久化  
        exclusive: false,//是否排他  
        autoDelete: false,//是否自动删除  
        arguments: null);//参数  
  
    //这里可以设置prefetchCount的值,表示一次从队列中取多少条消息,默认是1,可以根据需要设置  
    //这里设置了prefetchCount为1,表示每次只取一条消息,然后处理完后再确认收到,这样可以保证消息的顺序性  
    //global是否全局  
    channel.BasicQos(prefetchSize: 0, prefetchCount: 1, global: false);  
  
    Console.WriteLine(" [*] 正在等待消息...");  
  
    //创建消费者  
    var consumer = new EventingBasicConsumer(channel);  
    //注册事件处理方法  
    consumer.Received += (model, ea) =>  
    {  
        byte[] body = ea.Body.ToArray();  
        var message = Encoding.UTF8.GetString(body);  
        var email = JsonConvert.DeserializeObject<EmailDto>(message);  
        Console.WriteLine(" [x] 发送邮件 {0}", email.Email);  
        //处理完消息后,确认收到  
        //multiple是否批量确认  
        channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);  
    };    //开始消费  
    //queue队列名  
    //autoAck是否自动确认,false表示手动确认  
    //consumer消费者  
    channel.BasicConsume(queue: "email_queue",  
        autoAck: false,  
        consumer: consumer);  
  
    Console.WriteLine(" 按任意键退出");  
    Console.ReadLine();  
}	

一阶段测试效果

一阶段就是消费者和生产者能正常运行。

image
image

可以看到生产者发送邮件之后,消费者能够正常消费请求。那么开始二阶段,将邮件发送代码完成,并实现能够通过队列处理邮件发送。
对于邮件发送失败就简单的做下处理,相对较好的解决方案就是使用死信队列,将发送失败的消息放到死信队列处理,我这里就不用死信队列,对于死信队列感兴趣的可以查看往期文章:

开始编码(二阶段)

简单的创建一个用于发送邮件的类,这里使用
MailKit
库发送邮件。

public class EmailService  
{  
	private readonly SmtpClient client;  

	public EmailService(SmtpClient client)  
	{  
		this.client = client;  
	}  

	public async Task SendEmailAsync(string from, string to, string subject, string body)  
	{
		try
		{
			await client.ConnectAsync("smtp.163.com", 465, SecureSocketOptions.SslOnConnect); 
			// 认证  
			await client.AuthenticateAsync("zy1767992919@163.com", "");  

			// 创建一个邮件消息  
			var message = new MimeMessage(); 
			message.From.Add(new MailboxAddress("发件人名称", from));  
			message.To.Add(new MailboxAddress("收件人名称", to));  
			message.Subject = subject;  

			// 设置邮件正文  
			message.Body = new TextPart("html")  
			{  
				Text = body  
			};  

			// 发送邮件  
			var response =await client.SendAsync(message);  
			
			// 断开连接  
			await client.DisconnectAsync(true);  
		}
		catch (Exception ex)
		{
			// 断开连接  
			await client.DisconnectAsync(true);  
			throw new EmailServiceException("邮件发送失败", ex);  
		}
	}  
}  

public class EmailServiceFactory  
{  
	public EmailService CreateEmailService()  
	{  
		var client = new SmtpClient();  
		return new EmailService(client);  
	}  
}  
public class EmailServiceException : Exception  
{  
	public EmailServiceException(string message) : base(message)  
	{  
	}  

	public EmailServiceException(string message, Exception innerException) : base(message, innerException)  
	{  
	}  
}  

接下来我们在消费者中调用邮件发送方法即可,如果不使用死信队列,我们只需要在事件处理代码加上邮件发送逻辑就行了。

consumer.Received += async (model, ea) =>
{
	byte[] body = ea.Body.ToArray();
	var message = Encoding.UTF8.GetString(body);
	
	var email = JsonConvert.DeserializeObject<EmailDto>(message);
	
	// 创建一个EmailServiceFactory实例
	var emailServiceFactory = new EmailServiceFactory();  
	  
	// 使用EmailServiceFactory创建一个EmailService实例  
	var emailService = emailServiceFactory.CreateEmailService();  
	  
	// 调用EmailService的SendEmailAsync方法来发送电子邮件  
	string from = "zy1767992919@163.com"; // 发件人地址  
	string to = email.Email; // 收件人地址  
	string subject = email.Subject; // 邮件主题  
	string emailbody = email.Body; // 邮件正文  
	  
	try  
	{  
		await emailService.SendEmailAsync(from, to, subject, emailbody);  
		Console.WriteLine(" [x] 发送邮件 {0}", email.Email);
	}  
	catch (Exception ex)  
	{  
		Console.WriteLine(" [x] 发送邮件失败 " + ex.Message);  
		//这里可以记录日志
		//可以使用BasicNack方法,重新回到队列,重新消费
	}  
	
	
	//处理完消息后,确认收到
	//multiple是否批量确认
	channel.BasicAck(deliveryTag: ea.DeliveryTag, multiple: false);
};

在上面中可以将发送失败的邮件重新放队列,多试几次,这里就不做多余的介绍了。

完成效果展示

一封正确的邮件

ok,现在展示邮件发送Demo的完整展示。
首先我们来写一个正确的邮箱地址进行发送:

image
image
image

可以看到当我们发送请求之后,消费者正常消费了这条请求,同时邮件发送服务也正常执行。

多条发送邮件请求

那么接下来,我们通过Api测试工具,一次性发送多条邮件请求。其中包含正确的邮箱地址、错误的邮箱地址,看看消费者能不能正常消费呢~
这里简单的发送3条请求,2封正确的邮件地址,一封错误的,看看2封正常邮件地址的能不能正常发送出去。

这里有个问题,如果我填的邮件格式是正确的但是这个邮件地址是不存在的,他是能正常发送过去的,然后会被邮箱服务器退回来,这里不知道该怎么判断是否发送成功。所以我这的错误地址是格式就不对的邮件地址,用来模拟因为网络原因或者其他原因导致的邮件发送不成功。

image
image
image
image

可以看到3条请求都成功了,并且消费者接收到并正确消费了。2条正确邮件也收到了,1条错误的邮件也捕获到了。

总结

本文通过使用
RabiitMQ
点对点模式来完成一个发送邮件的小项目,通过队列去处理邮件发送。
通过
RabbitMQ.Client
库去连接RabbitMQ服务器。
使用
MailKit
库发送邮件。
通过使用RabbitMQ来避免邮件发送请求时间长的问题,同时能在消费者中重试、记录发送失败的邮件,来统一发送、统一处理。
不足点就是被退回的邮件不知道该如何处理。
可优化点:

  • 可以使用
    WorkQueues
    工作队列队列模式将消息分发给多个消费者,适用于消息量较大的情况。
  • 可以使用死信队列处理发送失败的邮件

参考链接

委托在现代代码中无处不在;委托是一种类型,它表示对具有特定参数列表和返回类型的方法的引用。开发人员使用委托将方法作为参数传递给其他方法。您可能熟悉的一个例子是事件处理程序。处理程序是可以通过委托调用的方法。委托让我想起 C++ 的函数指针,当然委托是完全面向对象的。

有几种方式来表示委托,例如,Func 委托。此泛型委托表示接受一个或多个参数并返回指定类型值的方法。下面是一个示例(带有lambda表达式):

Func<int, int> Multiplier = n => n * 5;int val = Multiplier(5);
Console.WriteLine(val);

这个概念的最新变体是 Action,它提供了更方便的简写。使用 Action时,不必显式地定义一个用单个参数封装方法的委托。下面是一个例子:

Action<string> outputFunc =GetOutputRoutine();
outputFunc(
"Hello, World!");static Action<string>GetOutputRoutine()
{
returnMyConsoleWriter;
}
static void MyConsoleWriter(stringinput)
{
Console.WriteLine(
"Console: {0}", input);
}

所以,这是一堂很好的课,但我为什么要提到这些呢?虽然我发现在编写代码时像传递参数一样传递方法很方便,但我也希望在调试时更容易跟踪。当然,您可以轻松地单步执行这些方法,但我通常希望在单步执行之前或之后快速导航到由委托表示的底层代码,随着 Visual Studio 17.10 的最新更新,这非常容易。

当您在调试时暂停时,您可以将鼠标悬停在任何委托上并获得一个方便的跳转,这里是一个带有Func委托的示例。

在本例中,Go to Source 清楚地表明,您将被重定向回 lambda 表达式。

请注意,这不仅仅适用于托管代码的情况,它还支持 C++ 函数指针和 std::function。

我们感谢您的反馈,帮助我们改进 Visual Studio,使其成为您最好的工具!您可以通过开发者社区与我们分享反馈,通过发送反馈来报告问题或分享您的建议,推动对新功能或现有功能的改进。

请在 Twitter 上与 Visual Studio Debugger 团队保持联系。

原文链接:https://devblogs.microsoft.com/visualstudio/easily-navigate-code-delegates-while-debugging/

1、为什么要使用虚拟内存

当我们没有多余的钱去购买大内存的云服务器时,但是当前服务器里面的软件和程序运行的比较多导致内存不够用了。这个时候可以通过增加虚拟内存来扩大内存容量。但是在启用虚拟内存时,需要仔细考虑系统的实际需求和硬件配置,以及权衡虚拟内存的优缺点,考虑好利弊后在开启虚拟内存。

2、什么是虚拟内存

虚拟内存是一种将部分物理内存扩展到磁盘空间的技术,以提供更大的内存空间和更好的系统性能。虚拟内存允许系统在物理内存不足时,将不常用的内存页转移到磁盘上,从而释放物理内存供活跃的进程使用。

使用虚拟内存的优点:

  • 增加可用内存:
    虚拟内存允许系统在物理内存不足时将部分内存数据存储到磁盘上,从而扩展了系统可用的内存空间。这样,即使系统的物理内存已经用尽,

    仍然可以继续运行应用程序,避免了因内存不足而导致的系统崩溃或应用程序崩溃。

  • 提高系统稳定性:
    虚拟内存可以防止系统因内存不足而崩溃。当物理内存不足时,Linux会将部分不活跃的内存页交换到交换空间中,从而释放物理内存供活跃的进程使用,提高了系统的稳定性和可靠性。

  • 支持大型程序运行:
    对于需要大量内存的应用程序,如数据库服务器或大型数据处理应用,虚拟内存可以提供额外的内存空间,使其能够在物理内存有限的情况下继续运行。

  • 灵活管理内存:
    虚拟内存允许系统管理员根据实际需求动态调整交换空间的大小。通过调整交换空间的大小,可以根据系统的负载情况和应用程序的内存需求来优化系统的性能和稳定性。

  • 提高系统响应速度:
    虚拟内存可以减少因内存不足而导致的系统响应速度下降的情况。当系统使用虚拟内存时,虽然会增加磁盘 I/O 操作,但可以保持系统运行,并在内存资源再次可用时恢复正常操作。

使用虚拟内存的弊端:

  • 性能影响:
    虚拟内存的使用可能会导致系统性能下降。当系统内存不足时,操作系统会将部分内存数据交换到磁盘上的交换空间中,这涉及到频繁的磁盘读写操作,会增加系统的响应时间和延迟。
  • 磁盘空间消耗:
    交换空间占用磁盘空间,可能会导致磁盘空间不足的问题,特别是在磁盘本身就比较小的情况下。
  • SSD 磨损:
    如果交换空间设置在 SSD 上,频繁的写入操作可能会加速 SSD 的磨损,缩短其寿命。
  • 应用程序性能下降:
    当系统使用虚拟内存时,应用程序可能会因为频繁的磁盘读写而性能下降,尤其是对于需要大量内存的应用程序。
  • 系统稳定性:
    如果交换空间设置不当或者交换空间不足,可能会导致系统稳定性问题,如系统死锁或应用程序崩溃。

总的来说,虚拟内存的使用可以提高系统的稳定性、可用性和性能,使系统能够更好地应对变化的工作负载和内存需求。但是在启用虚拟内存时,需要仔细考虑系统的实际需求和硬件配置,以及权衡虚拟内存的优缺点。在某些情况下,可能需要调整交换空间的大小或者考虑其他内存管理策略来优化系统性能。

3、物理内存

物理内存是用于存储正在运行的程序和数据的地方,读写速度低于CPU,但是高于磁盘。当应用程序需要使用内存时,它会向操作系统请求内存分配。操作系统会将需要的数据载入物理内存中,并管理内存的分配和释放。当物理内存不足时,系统将部分数据从物理内存交换到磁盘上的交换区(Swap),这样可以释放物理内存,以便给其他进程使用。将数据交换到Swap会导致性能下降。因此系统管理员和开发人员最好要合理规划和管理物理内存,确保系统具有足够的内存来运行应用程序和提供良好的性能。

4、Swap介绍

Swap是Linux系统中的一种虚拟内存技术,用于辅助物理内存(RAM)的管理。当物理内存不足时,Swap提供了一种将部分内存内容交换到硬盘上的方法,以释放物理内存供其他进程使用。当物理内存不够的时候,会把不活跃的进程暂时存储到交换区。当需要这条进程时就从交换区重新加载到内存,否则它不会主动交换到物理内存中。swap有三种交换方式:交换分区(Swap Partition)、交换文件(Swap File)、交换空间(Swap Space)。

swap相关使用命令:

显示当前启用的交换分区和交换文件的摘要信息,包括路径、大小和使用情况:

swapon -s

swapon -show

启用指定的交换分区或交换文件:

swapon /opt/swapfile     # 启用交换文件

swapon /dev/vda1         # 启用交换分区

创建交换分区或交换文件:

mkswap swapdata

关闭正在使用的交换分区或交换文件

swapoff /opt/swapfile     # 关闭交换文件

swapoff /dev/vda1         # 关闭交换分区

多个swap设置优先级, 优先级相同将同时使用, 在0到32767中间选一个数字

swapon -p 0 /opt/swapfile   # 交换文件路径

5、虚拟内存设置

通过设置交换文件设置虚拟内存。

5.1、查看可用磁盘空间
df -h
5.2、 使用
dd
工具来创建一个交换文件
dd if=/dev/zero of=/opt/swapdata bs=1024 count=16777216

参数说明:

  • dd

    dd
    用来创建交换文件。
  • if=/dev/zero

    if
    参数表示输入文件(input file)的路径。/dev/zero`是一个特殊设备文件,它会提供一系列的零字节。
  • of=/opt/swapdata

    of
    参数表示输出文件(output file)的路径。指定了交换文件的路径为
    /opt/swapdata
    。选择其他路径和文件名。
  • bs=1024

    bs
    参数表示块大小(block size),指定
    dd
    命令一次传输的数据块大小为1024字节(即1KB)。根据需求进行调整。
  • count=16777216
    :创建一个16GB大小的交换文件。与
    bs
    参数相乘,即16GB = 1024 * 1024 * 16 可以根据需要调整此参数来大小

创建成功在/opt目录生成一个swapdata文件

5.3、 设置交换文件的权限为只有 root 用户能读写:
chown root:root /opt/swapdata # 交换文件的所有者和所属组

chmod 600 /opt/swapdata   #只有root用户能读写

5.4、 创建文件类型设置为交换文件
mkswap /opt/swapdata # 交换文件路径
5.5、激活文件:指定的交换文件
swapon /opt/swapdata

激活成功后就可以使用
swapon -s
查看交换文件的摘要信息了

5.6、机器重启的时候自动挂载Swap
# 进入/etc/fstab文件进行修改。
vim /etc/fstab

#末尾追加
/opt/swapdata swap swap defaults 0 0

5.7、配置swap分区的使用机制

对于使用多大比例内存之后开始使用swap,在系统配置文件中可以通过调整参数进行修改。查看比例信息:

cat /proc/sys/vm/swappiness

该参数范围为0-100。0 就是最大限度使用内存,尽量不使用swap。100是积极使用swap。越高越会使用swap分区。重新配置值:

sysctl vm.swappiness=30

如需永久配置:

echo "vm.swappiness = 30" >> /etc/sysctl.conf
5.8、 查看使用情况

通过
free -h
命令查看内存使用情况。

6、关闭虚拟内存

关闭 swap 文件

swapoff /opt/swapdata

删除swap分区文件

rm -rf /opt/swapdata

删除fstab文件里追加的swap分区开机自动挂载配置内容

/opt/swapdata swap swap defaults 0 0

本文分享自华为云社区
《深度解读昇腾CANN多流并行技术,提高硬件资源利用率》
,作者:昇腾CANN。

随着人工智能应用日益成熟,文本、图片、音频、视频等非结构化数据的处理需求呈指数级增长,数据处理过程从通用计算逐步向异构计算过渡。面对多样化的计算需求,昇腾AI处理器内置丰富的硬件计算资源用于处理不同的计算任务。其中,AI Core、Vector Core与AI CPU分别负责AI计算场景下的矩阵、向量与标量计算,DVPP支持图像、视频等数据的加速处理,而HCCL作为华为集合通信库,则提供单机多卡及多机多卡间的数据并行、模型并行集合通信方案。

1.png

在给定硬件算力的情况下,如何高效利用这些计算资源、提高计算效率显得尤其重要。多样化的计算任务以task的形式下发到各硬件资源执行,GE(Graph Engine)图引擎采用多流并行算法,在满足计算图任务内部依赖关系的前提下,支持高效并发执行计算任务,从而大大提高硬件资源利用率和AI计算效率。

1 多流并行技术实现

计算图编译时,GE会为计算图中的每个节点分配一个硬件资源(即对应一种执行引擎),在任务执行时按编译时分配的stream调度顺序下发到对应的引擎执行。

各引擎使用不同的硬件计算资源,若同一时间只能执行某种引擎的一个task,则其余引擎会处于闲置状态,导致硬件资源严重浪费,影响端到端性能。若采用多流并行技术,在满足依赖关系的前提下,将不同task下发到对应的引擎上,驱动各个引擎并行执行,则可大大提升硬件资源的利用率。

GE采用了多流并行算法,将计算图的拓扑结构、硬件资源规格和执行引擎作为计算要素,为每个节点分配Stream。Stream与硬件资源绑定,任务执行时会按编译时分配的stream调度顺序下发到对应的引擎执行。同一Stream上的任务串行执行,不同Stream间的任务并发执行,从而提升硬件计算资源利用率。

GE多流并行技术的实现流程如下:

1. 基于网络节点功能和硬件资源特性,给每个节点分配执行引擎。

2. 基于网络拓扑结构和每个节点的执行引擎,为每个节点分配Stream。分配Stream时会同时考虑硬件规格、资源利用率等,提升并发度。

3. 不同Stream间可以进行同步来保证执行时序。

GE多流并行主要包含以下场景:

1. 计算与通信引擎并行:计算算子(如Convolution、Add等)会生成计算task,通信算子(HcomAllReduce等)会生成卡间通信task,两类task无拓扑依赖时可并发执行。

2.png

2. 不同计算引擎并行:矩阵运算(AI Core)、向量运算(VectorCore)和图像预处理(DVPP)等不同引擎的task,可下发到不同的引擎上并发执行。

3.png

3. 相同计算引擎内并行:当计算图中某个节点无法占满一个计算引擎的全部计算资源,且拓扑结构可并发时,该引擎的不同拓扑集合的task可并发执行。

4.png

2 多流并行执行效果

并行执行效果跟网络拓扑结构、节点引擎类型、AI处理器能力等因素存在相关性,理论最优并行场景下,整网执行时长为耗时最长的Stream的执行时长,其余Stream的执行时长都掩盖在该Stream的时长内。如下图所示,通信耗时可以掩盖在计算耗时内,向量计算耗时可以掩盖在矩阵运算耗时内。

5.png

基于Atlas 800I A2推理产品,在经过计算通信流水并行优化后,LLaMA-65B参数模型全量图执行性能提升30%左右,盘古系列71B参数模型全量图执行性能提升15%左右。

然而,多流并行是一种资源换执行效率的技术,会占用更多的Device流资源,一般来说,静态shape场景下开启多流并行后,内存占用增加7%左右,用户可结合实际情况选择使用。

3 如何使能多流并行技术

GE的多流并行技术是基于深度学习计算图模式下的计算优化手段,在静态shape的离线推理场景和Pytorch框架的计算图模式下默认使能多流并行技术,开发者可通过相应的参数enable_single_stream灵活控制。

import torchair astng
config
=tng.CompilerConfig()
# 关闭图单流执行功能
config.ge_config.enable_single_stream
=False
# 开启计算通信并行功能
config.experimental_config.cc_parallel_enable
=True
npu_backend
= tng.get_npu_backend(compiler_config=config)
...
model
=Model()
model
= torch.compile(model, backend=npu_backend, dynamic=False)

4 获取学习资源

GE多流并行技术的介绍就到这里,欢迎大家关注后续技术分享。如需获取更多学习资源请登录
昇腾社区

点击关注,第一时间了解华为云新鲜技术~