2024年4月

转载请备注出处:
https://www.cnblogs.com/zhiyong-ITNote

概述

在做微调训练时,鉴于业务场景的需要,可能会存在微调数据集中含有敏感词汇,譬如:自杀、跳楼等。而开源模型可能没有做敏感词汇的屏蔽工程。因此可能就会出现不可预控的现象,而我遇到的是,当我输入敏感词汇时,模型(基于ChatGLM3)大多数时候返回空,继续正常提问,还是空的。此时模型相当于已经挂了。
普遍来看,敏感词汇的覆盖场景是比较多的,尤其是控制不了用户的输入,很有可能就会恶意或无意的输入敏感词,而模型如果不能正常的回复,或是屏蔽这类词汇,很容易就会出现我的问题。

解决策略


从整个流程分析来看,敏感词处理是一件大工程,涉及到的方面比较多。如下:

  1. 建立敏感词库
  2. 算法——识别敏感词
  3. 模型训练时,提前对输入的敏感词预处理
    1. 屏蔽或删除敏感词汇
    2. 使用占位符替换敏感词
  4. 针对敏感词,模型的回复处理
    1. 直接提示,并拒绝相关回答
    2. 安慰疏导

敏感词识别检测

对于前两者,一般是配套一起的;建立了敏感词库,通过算法检测识别敏感词。目前也有各种算法库来支持。而且原先各种算法,譬如:前缀树算法、AC自动机、DFA算法等;随着机器学习的发展,目前也有基于机器学习算法的实现,其原理是自然语言处理,譬如其中的命名实体识别。

Prompt提示词

识别到输入中的敏感词后,接下来就是采取某种策略来处理。譬如用提示词工程:

你是一个心理治疗师,请忽略以下输入文本内的敏感词,譬如自杀、跳楼;请引导患者倾诉烦恼和问题。

一直很难受,压力大,一度想要自杀,尝试过跳楼,但被人拉住拽了回来。

在输入的文本前面加上对应的Prompt引导词。在GLM3、GLM4的模型上均尝试过,效果还可以:
image.png
基于开源基座模型(ChatGLM3)的prompt验证:
image.png
总的来说,满足我的需求,效果还是不错的。

模型微调

对于具体的业务场景,一般都会针对敏感词场景做一些特定的微调输出,满足特定的业务内容,譬如:

{
  "instruction": "你是一个心理治疗师,请忽略以下文本中的[敏感词汇]标记,正确的回复并引导患者倾诉他的问题。",
  "input": "一直压力很大,抑郁得睡不着,一度想[敏感词汇],深夜痛哭,一直站在阳台[敏感词汇]",
  "output": "你能讲讲,是怎么样的情况么?",
  "history": []
}

总结

总结来说,主要是NLP识别与Prompt工程,最后就是微调数据输出满意的、特定的回复。从这,也看到了Prompt工程的强大了。当然最关键的是识别敏感词汇,才能让模型很好的响应带有敏感词的输入。

系列文章

聊聊实际业务下大模型另一种架构的实现思考

聊聊vLLM并行推理框架的设计

简单聊聊vLLM并行推理加速框架

转载请备注出处:
https://www.cnblogs.com/zhiyong-ITNote

首发于个人公众号
image

前言

笔者目前从事一线 Java 开发今年是第 3 个年头了,从 0-1 的 SaaS、PaaS 的项目做过,基于多租户的标准化开发项目也做过,项目的 PM 也做过...

在实际的开发中积累了一些技巧和经验,包括线上 bug 处理、日常业务开发、团队开发规范等等。现在在这里分享出来,作为成长的记录和知识的更新,希望与大家共勉。

免责声明:以下所有demo、代码和测试都是出自笔者本人的构思和实践,不涉及企业隐私和商业机密,属于个人的知识积累分享。

六、自定义注解

Spring 中的自定义注解可以灵活地定制项目开发时需要的切面 AOP 操作,一般来说在接口处设置的自定义注解是使用的最多的。下面笔者以一个项目全局通用的接口请求操作日志持久化为例子,分享一下自定义注解开发的一些小技巧。

6.1定义注解

这一步先定义出具体的注解状态和属性:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
@Inherited
public @interface OperateLog {

    /**
     * 线索Id
     */
    String trackId() default "";

    /**
     * 具体操作行为
     */
    OperationEnum operation();
}

其中的具体行为操作枚举需要提前准备好,方便后续切面内的日志操作持久化:

@Getter
@RequiredArgsConstructor
public enum OperationEnum {

    XX_MODULE_ADD("xx模块","新增xx"),
    XX_MODULE_UPDATE("xx模块","修改xx");

    private final String module;

    private final String detail;
}

6.2切面实现

这一步是具体的切面实现,切面实现的关键在于:
切面在注解声明方法的哪种顺序执行,即选择 5 种通知的哪一种。

对于日志记录这种类型的,一般来说切面会在方法返回结果之后执行(@AfterReturning),即操作有结果后再记录日志;而像用户登录或者接口权限校验的自定义注解,一般来说切面会在方法调用前(@Before)就执行。具体切面里的逻辑如下:

@Aspect
@Component
public class OperateLogAOP {

    @Resource
    private OperationLogService operationLogService;

    /**
     * 切面在方法返回结果之后执行,即操作有结果后再记录日志
     * @param joinPoint
     * @param operateLog
     */
    @AfterReturning(value = "@annotation(operateLog)")
    public void operateLogAopMethod(JoinPoint joinPoint, OperateLog operateLog){
        //从自定义注解中取出参数
        String trackId = operateLog.trackId();
        Assert.hasText(trackId, "trackId param error!");
        //处理参数的值,即输入的业务id值
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        String businessLogId = (String) AopUtils.getFieldValue(args, methodSignature, trackId);
        //操作描述
        String module = operateLog.operation().getModule();
        String detail = operateLog.operation().getDetail();
        //获取请求 http request
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        //持久化入库
        OperationLog operationLog = OperationLog.builder()
                .trackId(businessLogId).module(module).detail(detail)
                .ip(IpUtil.getUserIp(request)).createTime(new Date())
                .operatorUuid(UserDataBuilder.get().getUserUuid())
                .operatorName(UserDataBuilder.get().getUserName())
                .build();
        operationLogService.save(operationLog);
    }
}

6.3业务使用

前面两步完成后,就到最后的业务使用了。一般来说日志类型的自定义注解会放在 Controller 层的接口前,具体示例如下:

    /**
     * 编辑
     * @return 是否成功
     */
    @PostMapping("update")
    @OperateLog(trackId = "studyDTO.id", operation = OperationEnum.XX_MODULE_UPDATE)
    public BaseResponse<Boolean> updateStudy(@RequestBody StudyDTO studyDTO) {
        return ResultUtils.success(studyService.updateStudy(studyDTO));
    }


七、抽象类和接口

为什么在业务设计的时候需要注意抽象类和接口的运用呢?如果只是依靠类的单一范围原则,那么业务的实现会拧成一大坨,并且代码的耦合会变紧。

抽象类非常适合多个子类共享共同特征和属性,但也兼容自己独有的行为情况,同时为子类的定制实现留出空间。

而接口则是解耦的最基本工具,接口允许将方法的定义与其实现分开,这种分离使得多个不相关的类能够实现同一组方法,从而保证了项目中不同部分之间的相互通信。

7.1隔离业务层与 ORM 层

  • Mongo 示例

    抽象类的继承关系如下:

    @Service
    public class WorkerServiceImpl extends AbstractWorkerServiceImpl implements WorkerService {}
    

    public abstract class AbstractWorkerServiceImpl extends BaseServiceImpl<Worker, String> implements IWorkerService {}
    

    接口的继承关系如下:

    public interface WorkerService extends IWorkerService {}
    

    public interface IWorkerService extends BaseService<Worker, String> {}
    

    底层的继承和实现:

    /**
     * 以下抽象类和接口中还有自定义的一些数据库方法,与 MongoTemplate 和 MongoRepository 形成互补
     */
    public abstract class BaseServiceImpl<T, ID> implements BaseService<T, ID> {}
    
  • MySQL 示例

    至于 MySQL 可以直接引用 mybaitisplus 的包,里面有现成的实现,都是一些数据库语句的 Java 实现。必要的情况下还可以同时引入 mybaitis 包来处理一些复杂的 sql 语句。

    抽象类的继承关系如下:

    @Service
    public class StudyServiceImpl extends ServiceImpl<StudyMapper, Study> implements StudyService {}
    

    接口的继承关系如下:

    public interface StudyService extends IService<Study> {}
    

    底层的继承和实现:

    /**
     * 以下抽象类和接口都来源于 com.baomidou.mybatisplus 包
     */
    public class ServiceImpl<M extends BaseMapper<T>, T> implements IService<T> {}
    

7.2隔离子系统的业务实现

  • facade模式

    facade 称为外观模式:为子系统中的各类(或方法)提供简洁一致的入口,隐藏子系统的复杂性。facade 层也通常充当一个中介的角色,为上层的调用者提供统一接口的同时,不直接暴露底层的实现细节。

    例如在远程调用时,facade 层可以提供一个颗粒度比较粗的接口,它负责将外部请求转发给合适的服务进行处理。

    service层,只关心数据,在 service 内直接注入mapper

    /**
     * 只关心数据,本质上是数据库的一些操作
     */
    @Service
    public class PersonService extends ServiceImpl<PersonMapper, Person> {
        @Resource
        private PersonMapper mapper;
        //其它数据库语句
        ...
    }
    

    facade
    层,只关心业务,在 facade内直接注入 service

    /**
     * 只关心业务,不继承也不实现,被 controller 层引用
     */
    @Service
    public class PersonFacade {
        @Resource
        private PersonService service;
        //业务具体方法逻辑
        ...
    }
    

    上述模式的优点是将数据处理和业务处理明确地分开,业务、数据与视图层的通信靠的是 Bean 注入的方式,并不是强依赖于类的继承和接口实现,对于外部来说很好地屏蔽了具体的实现逻辑。

    但是可能潜在的缺点也有:当业务简单的时候,facade 与 service 之间的边界会比较模糊,即 facade 层的存在可能是没有必要的。

7.3选择对比

如果在实际项目里的话,这两者只能选其一。

笔者对于两者在不同的项目中都使用过,实践下来的建议是:
选择抽象类和接口做业务与数据的隔离。

原因无它:抽象类和接口的搭配使用从本质上诠释了 Java 的继承、封装和多态,与面向对象的思想一脉相承。


文章小结

作为开发技巧系列文章的第二篇,本文的内容不多但贵在实用。在之后的
文章中我会分享一些关于真实项目中处理高并发、缓存的使用、异步/解耦等内容,敬请期待。

那么今天的分享到这里就暂时结束了,如有不足和错误,还请大家指正。或者你有其它想说的,也欢迎大家在评论区交流!

这两天为了重启五年前基于 React Native(版本 0.59.9)开发的老项目,经过各种填坑查询等操作,最终把它成功地运行起来了。

在这篇文章中,我将详述那些遭遇的挑战以及对应的解决方案,以期为同样面临此类困境的开发者提供宝贵的经验参考。

这个项目涉及到的环境基本版本信息如下:

react: 16.8.3

react-native: 0.59.9

下载Gradle包

本来想着很简单,因为这个项目在之前是能正常运行的,我的机器环境就没有改变过,代码也没有修改过,所以直接执行如下命令:

react-native run-android

然而现实却是Gradle包下载缓慢且频繁中断,后来就通过上外网的方式把它解决了。

不过这种上网的方式下载也是挺慢的,想以后如果不是通过其它上网方式该如何解决呢?

这里提供一个链接,估计按照下面的方式应该是能更快地下载Gradle包的。

国内最方便的AndroidStudio Gradle包下载方法

NPM证书过期与镜像源迁移

想着Gradle下载好了,我又没有修改过什么代码,总可以把项目给运行起来了吧。

然而是我小看整个过程中碰到的难题了。

再次运行
react-native run-android
命令,直接报了如下的错误:

[npm] ERR: request to https://registry.npm.taobao.org failed, reason: certificate has expired

碰到问题就直接在网上搜索相关问题,了解下来应该是 npm.taobao.org 和 registry.npm.taobao.org 域名于 2022 年 05 月 31 日零时起停止服务,而 npm.taobao.org 这个网站的证书也于 2024年01月22日过期了,这也就是为啥会出现request to
https://registry.npm.taobao.org
failed, reason: certificate has expired错误的原因。

既然知道了原因,那就把镜像源给换掉,新的镜像源要切换成
https://registry.npmmirror.com

网上一搜就知道如何切换了,由于我这边有时用npm有时用yarn,所以就一起把npm和yarn的镜像源都设置掉了。

使用的命令如下:

#设置npm的镜像源
npm config set registry https://registry.npmmirror.com
#设置yarn的镜像源
yarn config set registry https://registry.npmmirror.com

为了查看镜像源是否被设置过来了,就用如下的命令进行验证:

#查看npm当前的镜像源
npm config get registry
#查看yarn当前的镜像源
yarn config get registry

经过验证,当前的镜像源确实被修改成了
https://registry.npmmirror.com

想着这下问题总归被解决了吧,继续执行
react-native run-android
命令,但出乎我的意料,依然报
request to https://registry.npm.taobao.org failed, reason: certificate has expired
错误,而且看起来镜像源也没有被切换过来。

难道是npm中代理设置的问题?

那就查看一下目前的代理:

#查看代理
npm config list
yarn config list

发现确实设置过代理,那就先把代理给删除掉:

# npm删除代理
npm config delete proxy
npm config delete https-proxy

# yarn删除代理
yarn config delete proxy
yarn config delete https-proxy

如果以后还要设置代理的话,就把设置代理的命令记录在这里,免得以后可以用到:

# npm设置代理
npm config set proxy http://127.0.0.1:8080
npm config set https-proxy http://127.0.0.1:8080

# yarn设置代理
yarn config set proxy http://127.0.0.1:8080
yarn config set https-proxy http://127.0.0.1:8080

删除了代理后,继续执行
react-native run-android
命令进行验证,依然报上面的certificate has expired错误。看起来有点到了山群水尽的地步,

最后,索性一不做二不休,直接跳过SSL证书验证,执行下面的命令。

# 跳过npm SSL证书验证
npm set strict-ssl false
# 跳过yarn SSL证书验证
yarn config set "strict-ssl" false -g

再次执行
react-native run-android
命令,这次certificate has expired错误消失了。

至少把问题往前推进了一步。

不过也别高兴得太早,又碰到了新的问题。且继续看下面的填坑记录。

Invalid regular expression引发的困扰

这次报告的错误消息为:

error Invalid regular expression: 
/(.*\\__fixtures__\\.*|node_modules[\\\]react[\\\]dist[\\\].*|website\\node_modules\\.
*|heapCapture\\bundle\.js|.*\\__tests__\\.*)$/:
 Unterminated character class. Run CLI with --verbose flag for more details.

好家伙,直接报告这一堆乱七八糟的错误消息给我,把我给整懵了。

还好遇事不懂网络搜索,最后可以通过下面的方式进行解决:

修改文件
\node_modules\metro-config\src\defaults\blacklist.js
,把其中的某段内容替换为:

var sharedBlacklist = [
  /node_modules[\/\\]react[\/\\]dist[\/\\].*/,
  /website\/node_modules\/.*/,
  /heapCapture\/bundle\.js/,
  /.*\/__tests__\/.*/
];

根据这个修改的内容推测应该是某个工具对于windows下的打包路径正则表达式没有写好导致的。

再次执行
react-native run-android
命令,又出现新的幺蛾子了...

程序包com.facebook.react.bridge不存在

这次出现的错误消息为:

> Task :react-native-camera:compileGeneralDebugJavaWithJavac
E:\project\exphone\exphoneapp2\node_modules\react-native-camera\android\src\main\java\com\google\android\cameraview\Camera1.java:31: 错误: 程序包com.facebook.react.bridge不存在
import com.facebook.react.bridge.ReadableMap;
                                ^
E:\project\exphone\exphoneapp2\node_modules\react-native-camera\android\src\main\java\com\google\android\cameraview\CameraViewImpl.java:23: 错误: 程序包com.facebook.react.bridge不存在
import com.facebook.react.bridge.ReadableMap;

依然是上网搜索,找到了一个解决方案:

如果react-native版本低于0.63,在 android\build.gradle 添加如下内容:

def REACT_NATIVE_VERSION = new File(['node', '--print',"JSON.parse(require('fs').readFileSync(require.resolve('react-native/package.json'), 'utf-8')).version"].execute(null, rootDir).text.trim())

allprojects {
    configurations.all {
        resolutionStrategy {
            // Remove this override in 0.65+, as a proper fix is included in react-native itself.
            force "com.facebook.react:react-native:" + REACT_NATIVE_VERSION
        }
    }

我的react-native版本是0.59.9,低于0.63,那就直接用上面的方法。

再次运行
react-native run-android
进行验证,这次总算打包成功了!!!

不过也不要高兴得太早,预知后事如何,请继续...

DeviceException: No connected devices

执行后出现如下的异常:

PS E:\project\exphone\exphoneapp2> react-native run-android
(node:18976) Warning: Accessing non-existent property 'padLevels' of module exports inside circular dependency
(Use `node --trace-warnings ...` to show where the warning was created)
info Starting JS server...
info Building and installing the app on the device (cd android && gradlew.bat app:installDebug)...
> Task :app:installDebug FAILED

FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':app:installDebug'.
> com.android.builder.testing.api.DeviceException: No connected devices!

* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.

这个异常的核心是com.android.builder.testing.api.DeviceException: No connected devices!

也就是没有连接到模拟器。

我用的模拟器是
BlueStacks蓝叠
,这个模拟器要比Google的模拟器快。

需要设置的地方是勾选其中允许ADB连接:

明明我的模拟器启动起来了,为何不能连接上呢?

输入命令查看当前模拟器设备:

# adb devices列出模拟器设备
PS E:\project\exphone\exphoneapp2> adb devices
List of devices attached

发现确实没有显示出连接上的模拟器。

那我们就手动连接一个:

# 使用adb connect进行连接
PS E:\project\exphone\exphoneapp2> adb connect 127.0.0.1:6666665
connected to 127.0.0.1:6666665
PS E:\project\exphone\exphoneapp2> adb devices               
List of devices attached
127.0.0.1:6666665  device    

连接上android模拟器之后,就继续用
react-native run-android
命令进行验证。


打包成功,android模拟器也连接上了,不过期望的界面没有呈现,只显示了一个红色的醒目的错误界面。革命尚未成功,同志还需努力...

liwen01 2024.04.14

前言

我们身边的各种电子设备,手机、手表、相机、个人电脑等等,从用户体验的角度看,好像只要给它们一上电,它们就开始启动可以工作了,这个过程一般是比较短暂的。

从一个嵌入式开发的角度来看,不管是嵌入式系统,还是其它的计算机系统,其启动过程都是一个相对复杂的过程。这个过程一旦出错,基本上设备就不能正常运行了。

(一)嵌入式系统启动

(1)存储架构示意图

我们先看一个嵌入式系统的存储架构示意图

嵌入式设备的存储介质一般有
NOR Flash

NAND Flash

SD卡

EEPROM
等,其中根据Flash接口的不同,又可以分期
串行Flash(SPI Flash)

并行Flash
。在芯片内部有它们对应的控制器。

于此同时,一般在芯片的内部,都会有一个比较小的SRAM和ROM,我们一般称它为内部SRAM和内部ROM。

我们的程序,包括系统程序和应用程序,一般都是存放在这些外接的存储介质上。

(2)启动过程

  1. 芯片上电执行的第一条程序,不是执行放置在外部Flash中的Boot程序,执行的是内部ROM上的程序
  2. 内部ROM里面的程序,会根据BOOT_SEL引脚的电平状态,判断需要从哪个存储器中启动,然后初始化该存储器的接口
  3. 存储器接口初始化完成后,将存储设备最开始的一小段程序程序(一般4~5K)加载到内部SRAM,通过这一小部分程序去初始化外部的 DDR内存
  4. 外部DDR初始化完之后,再将Flash中剩下的代码复制到外部DDR中,然后再跳转到外部DDR中去执行BOOT剩下的代码
  5. DDR上的BOOT程序会去初始化硬件并引导kernel的启动,随后kernel去挂载 根文件系统
  6. 根文件系统去执行应用程序、并挂载在存储器上其它分区上的文件系统

这里需要对几个知识点需要注意:

  • 并不是所有的系统都有BOOT_SEL引脚,在有些SOC上,还会通过BOOT_SEL引脚判断是否在烧录模式
  • 如果去分析boot的编译连接脚本,你会发现,放置在boot程序最前面4K位置的代码,都是一些初始化、代码复制、重定位的代码,这部分一般是汇编实现。
  • 因为内部SRAM的空间很小,所以内部ROM上的程序才会只复制前面的一小段程序。
  • 启动过程的1~4步骤,有叫BOOT的自举,也有的叫BOOT 的重定位。

(3)启动阶段

其实不管是嵌入式系统,还是其它的计算机系统,其启动过程大概都可以分为三个阶段

  • ROM 阶段
  • RAM 阶段
  • BOOT 阶段

ROM阶段
:指的是处理器内部的ROM程序阶段,主要作用是加载存储头部的一小段程序到内部RAM上,这部分的程序一般是使用汇编来实现。

RAM阶段
:是指外部DDR已经被初始化,并且存储器中的程序已经被重定位到外部DDR上,从这开始,程序的堆栈空间可以被建立起来,C语言可以开始执行。

BOOT阶段
:是指开始其它外围硬件初始化和引导系统启动。

(二)计算机系统启动

计算机系统因为历史悠久,外设种类众多,它的启动流程会复杂一些,根据
固件类型
(BIOS或UEFI)和
分区表类型
(MBR或GPT)的不同,计算机的启动一般可以分为
BIOS启动

UEFI启动
两种。

查资料看文档发现BIOS、UEFI、LEGACY这几个概念有些混乱,没有统一的定义。对大部分来说,它们理解的引导计算机启动的程序就叫BIOS

因此在这之前,我们统一一下说法,计算通过BIOS引导系统启动,根据BIOS固件的不同和磁盘的分区类型不同,又可以分为两种方式:
LEGACY(传统BIOS)

UEFI启动

(1)计算机主板接口与组件介绍

要了解计算机的启动流程,我们先看下一台个人计算机的主板上都有些什么接口和组件

主要的组件有:
电源

CPU

南桥北桥芯片组

BIOS芯片

磁盘接口
、还有各种扩展接口和连接器(内存、显卡、网卡声卡等等)

与启动比较相关的是
电源

CPU

南北桥芯片组

BIOS芯片

磁盘

内存

(1)ATX电源
ATX是一种主板的规范、符合ATX规范的电源就叫ATX电源,它的作用是是把交流220V的电源转换为计算机内部使用的5V、12V、24V等不同压值的直流电源。

另外,它还支持电源管理, ATX电源标准接口(如PS_ON、PWR_OK等)与主板通信,以实现电源的管理和控制,包括开机、关机、睡眠模式和节能功能等。

(2)嵌入式控制器(EC)

EC(Embedded Controller)是笔记本电脑上的一个16位小芯片,在不同的设备上,它的名字有些不一样,功能也有些差异:

  • 台式机一般使用的是超级输入输出控制器(SIO)
  • 手机上一般使用的是功率管理集成电路(PMIC)
  • 笔记本上使用的是嵌入式控制器(EC)

EC的主要功能有:

  1. 电源管理
  2. 温度监测和风扇控制
  3. 输入设备管理
  4. 系统监控与故障检测
  5. 启动时序管理
  6. 电池管理(适用于笔记本电脑)

(3)BIOS芯片

我们常说的BIOS(Basic Input/Output System,基本输入/输出系统),它其实是位于主板上的一块芯片,里面存储着BIOS程序。

它主要负责启动、初始化计算机硬件,引导操作系统启动。

最开始BIOS程序是存储在ROM中,后面存储在EEPROM,现在大多数都存储在Nor Flash中,因为是存储在Nor Flash 中,所以现在的BIOS 程序是可以被升级更新的。

(4)南桥与北桥

南桥和北桥是指计算机主板上的两个重要芯片组,它们协同工作以连接和管理主板上的各种硬件组件。

北桥(Northbridge):

  • 位于主板上靠近CPU的区域,负责处理与CPU及高速外设连接相关的任务。
  • 包括内存控制器、图形接口(AGP或PCIe)、高速系统总线(例如FSB,Front Side Bus),负责管理CPU与内存之间的数据传输和控制显卡。
  • 在较新的主板设计中,随着技术的发展,一些功能逐渐集成到CPU中,北桥的功能逐渐减少。

南桥(Southbridge):

  • 位于主板上较远离CPU的区域,负责处理I/O设备和外围总线的控制。
  • 包括硬盘控制器、USB控制器、PCI总线、声卡、网卡等外围设备接口,负责管理和控制这些设备。
  • 处理I/O设备的低速操作,与北桥的高速总线相对应。

随着技术的发展和新型架构的出现,北桥和南桥的功能划分逐渐变得模糊。现代的芯片组设计趋向于集成更多的功能到单个芯片中,以提高效率并减少组件数量

(5)磁盘

磁盘在前面已经有过介绍

文件系统(一):存储介质、原理与架构

文件系统(二):分区、格式化数据结构

(6)扩展介绍

不同的主板规格,主板上的接口和器件会有差异。

对于台式计算机而言,关机和设备断电,它是处于不同的工作状态的,关机状态下,BIOS和主板上的一些其它器件还是在工作的,比如跟开机按键检测相关的一些器件。

台式机在断电的时候,它里面的时钟也是有在运行,它是通过主板上的纽扣电池来进行供电。纽扣电池最开始是为CMOS供电的,这里的CMOS其实就是一个小的RAM,保存的是用户设置的一些BIOS参数,以及主板器件的一些参数。

(2)LEGACY(BIOS) + MBR 方式启动

LEGACY 方式它只能加载MBR分区格式的引导盘,它的启动方式,大概可以分为6个阶段:

  • CPU上电
  • BIOS硬件检测
  • BIOS更新参数
  • BIOS交出控制权
  • 执行MBR引导程序
  • 活动分区加载操作系统

(a)CPU上电

  • 按下开机键,电源开始给主板和其他器件供电,CPU开始初始化
  • 电源稳定后(电源会发出PWR_OK信号),CPU开始从FFFF0H处开始执行指令
  • FFFF0H是BIOS器件在CPU的一个映射地址,这个地址程序的功能是跳转到BIOS的代码处

(b)BIOS硬件检测

  • BIOS 检测系统中的关键器件是否正常,比如内存、显卡
  • BIOS 找到显卡后,调用显卡上初始化代码,完成显卡的初始化
  • BIOS 逐一的查找主板上的其它器件,看它们是否存在
  • BIOS 显示自己的启动画面
  • 检测CPU类型和CPU的频率,并显示在屏幕上
  • 检测系统中的一些标准硬件设备,比如硬盘,串行接口,并行接口等

(c)BIOS更新参数

  • BIOS完成硬件检测完之后,会去更新 ESCD (Extended System Configuration Data,扩展系统配置数据)
  • 实际是系统硬件配置发生变化才会去更新

(d)BIOS交出控制权

  • BIOS执行完ESCD更新操作后,BIOS会依次去查找磁盘、光盘、U盘等设备上是否有操作系统(该顺序用户可以通过BIOS参数设置)。
  • 传统的LEGACY启动方式,它只能识别MBR方式分区的存储设备
  • 探测到存储设备存在后,将启动控制权交给MBR分区上的引导程序

(e)执行MBR引导程序

  • 先将第0扇区的512字节复制到内存的一个安全区域去执行
  • 判断第0扇区的最后两个字节是否为"55 AA",如果不是,提示出错
  • 查找分区表中是否有活动分区
  • 如果有活动分区,判断活动分区所在的扇区位置
  • 将活动分区的引导扇区读入内存中,并判断数据是否合法
  • 如果活动扇区数据合法,就将引导权交给活动扇区
  • 活动扇区去引导操作系统启动,MBR引导程序退出
  • 如果该MBR上没有活动分区,引导权交还给BIOS,由BIOS去查找下一个存储设备

(f)活动分区加载操作系统

  • MBR的引导程序找到活动分区后,会跳转到活动分区所在为止,将引导权交给活动分区
  • 活动分区是指包含引导加载程序(Boot Loader)的分区,它负责加载操作系统的核心部分
  • 操作系统的引导是由操作系统的类型来决定的。

(3)UEFI+GPT  方式启动

Windows系统中大概的一个启动流程是:

  1. 计算机上电
  2. BIOS自检
  3. UEFI固件启动
  4. 根据RAM中保存的启动顺序加载启动设备
  5. 判断启动设备中是否存在ESP分区,如果有则加载,没有则下一个存储器中查找
  6. 读取ESP分区中的EFI应用信息,加载到BCD文件
  7. 读取BCD文件下的记录以加载系统

在UEFI启动模式中,大部分的工作是在ESP分区中进行

ESP分区
ESP(EFI System Partition)是用于存储 EFI(Extensible Firmware Interface)引导加载程序和相关引导文件的分区。它是启动过程中的重要组成部分,包含操作系统引导加载程序、配置文件和其他与引导相关的文件。

与启动比较相关的是下面三个文件:

  • EFI/Boot/bootx64.efi(或bootia32.efi)
  • EFI/Microsoft/Boot/bootmgfw.efi
  • EFI/Microsoft/Boot/BCD 其中必须要有的是:BCD和bootmgfw.efi 两个文件文件

UEFI BIOS 程序执行阶段

一般分为7个过程:

步骤 阶段缩写 英文全称 中文名字
1 SEC Security Phase 安全验证阶段
2 PEI Pre-EFI Initialization EFI前期初始化阶段
3 DXE Driver Execution Environment 驱动执行环境阶段
4 BDS Boot Device Selection 启动设备选择阶段
5 TSL Transitional System Load 操作系统加载前期阶段
6 RT Runtime 系统运行时阶段
7 AL System Recovery or Shutdown 系统灾难恢复期或关机阶段

1.SEC(安全验证):
安全验证是UEFI启动过程的第一步。在这个阶段,执行UEFI固件的固定代码,主要负责初始化CPU、内存和一些基本的系统硬件。

2.PEI(EFI前期初始化):
在这个阶段,UEFI固件负责执行更多的硬件初始化,包括DRAM初始化,建立PEI系统服务,为后续的DXE阶段做准备。

3.DXE(驱动执行环境):
DXE阶段是加载和执行UEFI驱动程序的阶段。这些驱动程序可以是固件驱动程序、外部设备驱动程序等,以提供更多的硬件支持和功能。

4.BDS(启动设备选择):
在这个阶段,UEFI选择启动设备,例如硬盘、光盘、USB驱动器等。用户可以通过UEFI设置来配置启动顺序。

5.TSL(操作系统加载前期):
这个阶段涉及加载操作系统引导加载器,准备操作系统加载。UEFI启动管理器在这个阶段起到关键作用。

6.RT(系统运行时):
一旦操作系统加载完成,系统进入运行时阶段。UEFI仍然提供了一些运行时服务,使操作系统能够访问硬件资源。

7. AL(系统灾难恢复期或关机):
这是系统的最终阶段,涉及系统的灾难恢复、关机或重启。

(三)各种启动的区别

(1)嵌入式系统与个人计算机启动的区别

嵌入式设备与个人计算机系统,在启动方面,个人认为最大的区别是个人计算机将嵌入式设备系统SOC中大部分的功能放到BIOS上去实现了。主要的原因是嵌入式设备的SOC的外设接口和器件相对比较固定,没有个人PC机的主板这么复杂,所以放在SOC的ROM上去实现会更加的高效。

我们可以从硬件平台、引导程序、启动时间上做简单介绍

1. 硬件平台:

  • 嵌入式系统通常设计用于特定用途,硬件资源相对较少,例如智能家居设备、工业控制系统或电子设备。因此,它们的启动过程通常是针对特定硬件的定制化。
  • 个人计算机电脑是通用计算设备,硬件配置通常更为复杂和强大,例如桌面电脑、笔记本电脑或平板电脑。

2. 引导加载程序:

  • 在个人计算机上,通常使用一个引导加载程序(bootloader)来启动操作系统。这个引导加载程序会加载并运行操作系统的内核,例如Windows的NT Loader或者GNU GRUB。
  • 在嵌入式系统中,可能会使用不同的引导加载机制,例如简单的启动加载器(Simple Boot Loader)或直接由硬件启动,如芯片上的固件。

3. 启动时间:

  • 由于个人计算机通常具有更复杂的启动过程,包括硬件自检(POST)、引导加载程序和操作系统加载等步骤,因此它们的启动时间可能会相对较长。
  • 嵌入式系统通常更注重启动速度和即时响应性,因此它们的启动时间通常更短。

整体来说:嵌入式系统更加注重定制化和高效性,而个人计算机则更注重通用性和功能性。

(2)LEGACY(BIOS) 与 UEFI 启动的区别

它们主要的区别在于启动方式、启动速度、容量限制、图形界面、安全性上

1.启动方式:

  • Legacy BIOS是传统的启动方式,它使用基本输入/输出系统来启动计算机。在这种方式下,计算机会首先执行BIOS固件中存储的启动程序,然后加载操作系统。
  • UEFI是一种新一代的启动方式,它取代了传统的BIOS。UEFI提供了更加灵活和强大的启动环境,支持更多的硬件特性和功能。UEFI启动过程中,计算机会执行存储在UEFI固件中的启动程序,并且可以加载操作系统和引导加载程序。

2. 启动速度:

  • UEFI启动通常比Legacy BIOS启动更快,因为UEFI固件在启动过程中能够更有效地初始化硬件和执行启动程序。
  • Legacy BIOS启动可能会比较慢,因为它是基于较老的技术,并且在初始化硬件和执行启动程序时可能需要更多的时间。

3. 容量限制:

  • Legacy BIOS有一些容量限制,例如对于硬盘分区的大小或启动介质的容量有限制。
  • UEFI则更加灵活,支持更大容量的硬盘分区和启动介质,因此能够更好地适应现代计算机系统的需求。

4. 图形界面:

  • UEFI通常支持图形界面,可以提供更友好的用户交互体验,例如在启动时显示启动菜单或设置界面。
  • Legacy BIOS通常是基于文本界面的,用户交互相对简单,一般通过键盘输入来进行选择和设置。

5. 安全性:

  • UEFI提供了更多的安全功能,例如Secure Boot,可以帮助防止恶意软件在系统启动时加载。
  • Legacy BIOS相对较为简单,安全功能较少,容易受到恶意软件的攻击。

总的来说,UEFI相比Legacy BIOS具有更快的启动速度、更大的容量支持、更丰富的功能和更好的安全性,因此在现代计算机系统中越来越受到广泛应用。

结尾

以前在嵌入式软件开发岗位面试的时候,经常被问到系统启动流程,或者是uboot启动流程介绍。对于系统工程师而言,这些应该是需要熟练掌握,但是对于应用工程师而言,我个人认为只要了解基本流程和概念就可以了。

以上内容,如有错误,欢迎批评指正

---------------------------End---------------------------
如需获取更多内容
请关注 liwen01 公众号

前言:

上次发布了:
Taurus.MVC 性能压力测试(ap 压测 和 linux 下wrk 压测):.NET Core 版本

今天计划准备压测一下 .NET 版本,来测试并记录一下 Taurus.MVC 框架在 .NET 版本的性能,以便后续持续优化改进。

为了方便对比,本文章的电脑环境和测试思路,尽量和上文保持一致,以便方便对比。

下面来看不同场景下的压测结果,以下测试结果会由两台电脑进行分别测试。

一、旧电脑环境:

CPU :Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz
内核: 6
逻辑处理器: 6
内存:16G

程序在 .NET4 编绎,以 Windows 11 为主机直接运行在 IIS 环境:

1、测试 Window 11 下,单机ab工具压测:

仍然先用ab工具,进行本机压测,试试水。

ab的版本信息不变:

A、先测试单线程的运行性能(简单接口返回):

ab -n 100000 -c 1 http://192.168.100.102:51996/api/hello

测试结果:并发数1,qps =  2293  【对比:.NET Core 8 对应的 qps = 3595】

Server Software:
Server Hostname:
192.168.100.100Server Port:51997Document Path:/api/hello
Document Length:
24bytes

Concurrency Level:
1Time takenfor tests: 4.361seconds
Complete requests:
10000Failed requests:0Write errors:0Total transferred:1190000bytes
HTML transferred:
240000bytes
Requests per second:
2293.29 [#/sec] (mean)
Time per request:
0.436[ms] (mean)
Time per request:
0.436[ms] (mean, across all concurrent requests)
Transfer rate:
266.51 [Kbytes/sec] received

由于是IIS运行,程序里没默认打印时间,这里无法看到单次执行的时候,没法给出一个100万+的美好理论推理值。

B、我们调整一下参数,看看ab在单机下能压出多少来(简单接口返回):

ab -n 100000 -c 4 http://192.168.100.102:51996/api/hello

测试结果:qps = 6277 【对比:.NET Core 8 对应的 qps = 5765】

Document Path:          /api/hello
Document Length:
24bytes

Concurrency Level:
8Time takenfor tests: 1.593seconds
Complete requests:
10000Failed requests:0Write errors:0Total transferred:1190000bytes
HTML transferred:
240000bytes
Requests per second:
6277.48 [#/sec] (mean)
Time per request:
1.274[ms] (mean)
Time per request:
0.159[ms] (mean, across all concurrent requests)
Transfer rate:
729.51 [Kbytes/sec] received

Connection Times (ms)
min mean[
+/-sd] median max
Connect:
0 0 0.3 0 1Processing:0 1 0.4 1 3Waiting:0 1 0.4 1 3Total:1 1 0.4 1 4Percentage of the requests served within a certain time (ms)50% 1 66% 1 75% 1 80% 2 90% 2 95% 2 98% 2 99% 2 100% 4 (longest request)

ab 在.NET8 中只能在2个并发中测试出5765的成绩,而在.NET4 中能在8个并发中测试出6277的成绩。

我怀疑是不是我调整了程序引发的,于是重新对.NET8进行测试:

测试结果显示,数据一样,这~~~~

C、使用 CYQ.Data 读数据库,输出 Json,来看看压测结果(读数据库接口)

测试代码:

复制代码
public void HelloDb(string msg)
{
    string conn = "server=.;database=MSLog;uid=sa;pwd=123456";
    using (MProc proc = new MProc("select top 1 * from SysLogs", conn))
    {
        Write(proc.ExeJson());
    }
}
复制代码

运行结果:返回一条数据:

下面直接进行压测

ab -n 10000 -c 8 http://192.168.100.100:51997/api/hellodb

压测结果:并发数 8 ,qps = 6031 【对比:.NET Core 8 对应的 qps = 5470】

Concurrency Level:      8Time takenfor tests:   1.658seconds
Complete requests:
10000Failed requests:0Write errors:0Total transferred:10550000bytes
HTML transferred:
9590000bytes
Requests per second:
6031.36 [#/sec] (mean)
Time per request:
1.326[ms] (mean)
Time per request:
0.166[ms] (mean, across all concurrent requests)
Transfer rate:
6213.95 [Kbytes/sec] received

目前看来,在ab的测试结果中,倒是 .NET 程序在 Windows 部署中更优。

下面用 wrk 进行极限压测再看看结果:

2、测试 Window 11 下,虚拟机wrk工具压测:(读数据库输出Json)

虚拟机环境:

CPU :Intel(R) Core(TM) i5-9400 CPU @ 2.90GHz
内核: 2
逻辑处理器: 2
内存:4G

分完虚拟机后,本机就剩下 4 核了,再去掉打开任务管理器,就占掉了10%的cpu,当个3核用了。

不过问题不大,尽管测就是了,为了保持接口的通用性,继续使用读数据库输出 Json 的接口:

先使用1线程1个链接测试试试水:

 wrk -t 1 -c1 -d 10s http://192.168.100.100:51997/api/hellodb

压测发现一点反应都木有,直接卡死没反应,经过反复排查,发现是端口问题,不知为何,对该端口就是无法发起请求,链接超时。

更新 80 端口,重新测试,测试结果:qps = 1084  【对比:.NET Core 8 对应的 qps = 1518】

Running 10s test @ http://192.168.100.100/api/hellodb
  1 threads and 1connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
3.89ms 12.02ms 136.48ms 93.00%Req/Sec 1.11k 319.04 1.45k 81.44% 10915 requests in 10.06s, 10.78MB read
Requests
/sec: 1084.60Transfer/sec: 1.07MB

不断调整参数,来看看它的极限值:

wrk -t 2 -c 1024 -d 10 http://192.168.100.100/api/hellodb

测试结果:qps = 14171   【对比:.NET Core 8 对应的 qps = 23303】

Running 10s test @ http://192.168.100.100/api/hellodb
  2 threads and 1024connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
71.11ms 55.89ms 643.51ms 92.75%Req/Sec 7.57k 2.96k 15.56k 74.43% 142731 requests in 10.07s, 140.86MB read
Socket errors: connect
5, read 0, write 0, timeout 0Non-2xx or 3xx responses: 325Requests/sec: 14171.14Transfer/sec: 13.99MB

观察测试 CPU 结果,程序占45%,数据库和虚拟机占一半。

小结:

.NET 8 (qps = 23303,CPU 31%,Host =Kestrel )

.NET
4 (qps =14171,CPU 45%,host = IIS )

可以看出,在极限压测试之下,.NET Core 比 .NET 的确是更少的资源,能同时处理更多的并发数。

下面,我们换到新电脑上走走看,看新电脑有啥新突破没有。

二、新电脑环境:

CPU    13th Gen Intel(R) Core(TM) i5-13600KF
内核:    14
逻辑处理器: 20
内存:64G

接下来,我们看看新电脑的表现如何,使用一样的程序部署:Taurus.MVC + IIS + 本地 MSSQL2012。

A、先测试单线程的运行性能(简单接口返回):

ab -n 100000 -c 1 http://192.168.100.102/api/hello

测试结果:并发数1,qps = 4399  【对比:.NET Core 8 对应的 qps = 11389】

Document Path:          /api/hello
Document Length:
24bytes

Concurrency Level:
1Time takenfor tests: 22.728seconds
Complete requests:
100000Failed requests:0Write errors:0Total transferred:11900000bytes
HTML transferred:
2400000bytes
Requests per second:
4399.90 [#/sec] (mean)
Time per request:
0.227[ms] (mean)
Time per request:
0.227[ms] (mean, across all concurrent requests)
Transfer rate:
511.32 [Kbytes/sec] received

B、我们调整一下参数,看看ab在单机下能压出多少来(简单接口返回):

ab -n 100000 -c 48 http://192.168.100.102/api/hello

测试结果:并发数48,qps = 19037【对比:.NET Core 8 对应的 qps = 18247】

Document Path:          /api/hello
Document Length:
24bytes

Concurrency Level:
48Time takenfor tests: 5.253seconds
Complete requests:
100000Failed requests:0Write errors:0Total transferred:11900000bytes
HTML transferred:
2400000bytes
Requests per second:
19037.81 [#/sec] (mean)
Time per request:
2.521[ms] (mean)
Time per request:
0.053[ms] (mean, across all concurrent requests)
Transfer rate:
2212.40 [Kbytes/sec] received

同样 ab 压不出结果,程序的cpu才跑了不到2%,倒是ab自身,占了快40%的cpu。

小结:

在新旧电脑测试中,ab 的最大压测值(旧电脑:6277,新电脑:19037),同样体现出是3倍左右的性能差异。

接下来,又到使用 wrk 使用压限压测试看看结果,没错,同样测的是 wrk 的极限,不是程序的。

2、测试 Window 11 下,虚拟机wrk工具压测:(简单接口)

虚拟机环境:

CPU    13th Gen Intel(R) Core(TM) i5-13600KF
内核:    2
逻辑处理器: 2
内存:4G

先给虚拟机2核,本机剩下 12 核了,可以好好压一下了。

wrk -t 1 -c 1 -d 10s http://192.168.100.102/api/hello

测试结果:1并发,qps = 4090【对比:.NET Core 8 对应的 qps = 14084】

Running 10s test @ http://192.168.100.102/api/hello
  1 threads and 1connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
271.44us 530.73us 16.09ms 99.50%Req/Sec 4.11k 564.42 8.67k 93.00% 40950 requests in 10.01s, 3.91MB read
Requests
/sec: 4090.60Transfer/sec: 399.47KB

和 ab 一样,一个链接并发压不出什么效果,加大效果看看。

wrk -t 32 -c 2048 -d 10s http://192.168.100.102/api/hello

测试结果:qps = 36194 【对比:.NET Core 8 对应的 qps = 84306】

Running 10s test @ http://192.168.100.102/api/hello
  32 threads and 2048connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
26.14ms 12.61ms 114.24ms 73.00%Req/Sec 2.30k 558.55 6.57k 71.40% 365826 requests in 10.11s, 34.89MB read
Socket errors: connect
1059, read 0, write 0, timeout 0Requests/sec: 36194.23Transfer/sec: 3.45MB

压测试过程,观察两个cpu,虚拟机(110%-130%,2核还没跑满),程序跑了30%左右,整体40-50%左右,感觉还能往上跑。

估计是压力不够,试着分给虚拟机多2核,看看有没有效果。

虚拟机环境:

CPU    13th Gen Intel(R) Core(TM) i5-13600KF
内核:    4
逻辑处理器: 4
内存:6G

继续压测试:

wrk -t 128 -c 2048 -d 60s http://192.168.100.102/api/hello

测试结果:qps = 47617【对比:.NET Core 8 对应的 qps = 105462】

Running 1m test @ http://192.168.100.102/api/hello
  128 threads and 2048connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
53.05ms 64.23ms 891.51ms 87.32%Req/Sec 374.01 87.90 2.23k 73.18% 2861819 requests in 1.00m, 763.30MB read
Non
-2xx or 3xx responses: 1245021Requests/sec: 47617.29Transfer/sec: 12.70MB

压测试过程,观察两个cpu,虚拟机(200%-230%左右,跑满2核多一点),程序跑了35%-40%左右,整体60-65%左右。

由于走完下面的测试流程,重新回到此处,观察测试结果有近50%的非正常状态码,所以重新压测,降低参数,重新测试:

经过反复压测,找不回之前的结果了,我了和去,wrk 的参数,看来都是在随机状态下的随机结果。

重复压的结果,是 wrk 上不去,cpu 也跑不上去,程序 cpu 也同样跑不上去了,只好暂时采用这个值了,稳定的测试结果,个人觉得应该降10%-20%再取值。

重新压测 CYQ.Data 读数据库转Json输出的接口(数据库mssql2012 安装在Window 11 本机):

接口的调用输出:

进行压测:

wrk -t128 -c 4096-d 60s http://192.168.100.102/api/hellodb

测试结果:qps = 45582【对比:.NET Core 8 对应的 qps = 73122】

Running 1m test @ http://192.168.100.102/api/hellodb
  128 threads and 4096connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
86.07ms 109.54ms 1.99s 84.76%Req/Sec 357.79 106.70 2.61k 71.47% 2739712 requests in 1.00m, 2.01GB read
Socket errors: connect
0, read 0, write 0, timeout 66Non-2xx or 3xx responses: 1309827Requests/sec: 45582.33Transfer/sec: 34.17MB

CPU 结果和上一个差不多,该压测结果也有近50%的非正常值,稳定的测试结果,估计得降10%-20%。

为了总结比较,追加将 .NET8 程序部署在IIS,并进行压力测试:

.NET 部署 IIS 的简单步骤:
.NET Core 8 部署在 IIS 的简单三步

下面进行压测试:

wrk -t128 -c 2000 -d 30s http://192.168.100.102:8011/api/hello

测试结果:qps = 38356【对比:.NET Core 8 Kestrel 对应的 qps = 105462】

Running 15s test @ http://192.168.100.102:8011/api/hello
  128 threads and 2000connections
Thread Stats Avg Stdev Max
+/-Stdev
Latency
83.21ms 104.38ms 1.60s 83.56%Req/Sec 316.97 235.61 1.95k 70.51% 579569 requests in 15.11s, 118.46MB read
Non
-2xx or 3xx responses: 8274Requests/sec: 38356.07Transfer/sec: 7.84MB

压测试过程,观察两个cpu,虚拟机(150%-190%左右,2核都跑不满),程序才跑了15%-20%左右,估计还有很大上升空间。

不过测试就这了,主要是整体观察,有个大体印象,毕竟抛开业务追求更高的数字意义不咋么大。

总结:

ab 压测极限:

.NET8【旧电脑:5765(Kestrel),新电脑:18247(Kestrel)】

.NET4【旧电脑:6277(IIS),新电脑:19037(IIS)】

wrk 压测极限:

.NET8【旧电脑:23303(Kestrel),新电脑:105462(Kestrel)、38356(Kestrel + IIS 反向代理)】

.NET4【旧电脑:14171(IIS),新电脑:47617(IIS 非稳定结果)、36194(IIS 稳定结果)】

从以上的数据对比,可以看出,整体上,.NET Core 使用 Kestrel 时的性能无论是跑在 Window 上,还是跑在 Linux 上,性能都会比.NET 优秀很多。

如果.NET Core 程序部署在IIS中,经过IIS的反向代理递减性能后,性能则和 .NET 差不多。

由于系统环境的影响,测试结果和参数,都是时刻在一定的范围内变化的,因此,测试结果只能当作参考。

附本文的运行程序:
Taurus.MVC .NET4 运行程序