一般我们发布项目的时候通常都会采用release版本,因为release会在jit层面对我们的il代码进行了优化,比如在迭代和内存操作的性能提升方面,废话不多说,

我先用一个简单的“冒泡排序”体验下release和debug下面的性能差距。

一:release带来的闪光点【冒泡排序】

  这个是我多年前写的算法系列中的一个冒泡排序的例子,就随手翻出来展示一下,准备灌入50000条数据,这样就可以执行25亿次迭代,王健林说,不能太张

狂,几十亿对我来说不算小意思,算中等意思吧。

 namespace ConsoleApplication4
{
class Program
{
static void Main(string[] args)
{
var rand = new Random();
List<int> list = new List<int>(); for (int i = ; i < ; i++)
{
list.Add(rand.Next());
} var watch = Stopwatch.StartNew(); try
{
BubbleSort(list);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} watch.Stop(); Console.WriteLine("耗费时间:{0}", watch.Elapsed);
} //冒泡排序算法
static List<int> BubbleSort(List<int> list)
{
int temp;
//第一层循环: 表明要比较的次数,比如list.count个数,肯定要比较count-1次
for (int i = ; i < list.Count - ; i++)
{
//list.count-1:取数据最后一个数下标,
//j>i: 从后往前的的下标一定大于从前往后的下标,否则就超越了。
for (int j = list.Count - ; j > i; j--)
{
//如果前面一个数大于后面一个数则交换
if (list[j - ] > list[j])
{
temp = list[j - ];
list[j - ] = list[j];
list[j] = temp;
}
}
}
return list;
}
}
}

Debug下面的执行效率:

Release下面的执行效率:

从上面两张图可以看到,debug和release版本之间的性能差异能达到将近4倍的差距。。。还是相当震撼的。

二:release应该注意的bug

  release确实是一个非常好的东西,但是在享受好处的同时也不要忘了,任何优化都是要付出代价的,这世界不会什么好事都让你给占了,release有时候为了

性能提升,会大胆的给你做一些代码优化和cpu指令的优化,比如说把你的一些变量和参数缓存在cpu的高速缓存中,不然的话,你的性能能提升这么多么~~~

绝大多数情况下都不会遇到问题,但有时你很不幸,要出就出大问题,下面我同样举一个例子给大家演示一下:

     class Program
{
static void Main(string[] args)
{
var isStop = false; var task = Task.Factory.StartNew(() =>
{
var isSuccess = false; while (!isStop)
{
isSuccess = !isSuccess;
}
}); Thread.Sleep();
isStop = true;
task.Wait(); Console.WriteLine("主线程执行结束!");
Console.ReadLine();
}
}

上面这串代码的意思很简单,我就不费劲给大家解释了,但是有意思的事情就是,这段代码在debug和release的环境下执行的结果却是天壤之别,而我们的常规

思想其实就是1ms之后,主线程执行console.writeline(...)对吧,而真相却是:debug正常输出,release却长久卡顿。。。。一直wait啦。。。。这是一个大

bug啊。。。不信的话你可以看看下面的截图嘛。。。

debug:

release:

三:问题猜测

刚才也说过了,release版本会在jit层面对il代码进行优化,所以看应用程序的il代码是看不出什么名堂的,但是可以大概能猜到的就是,要么jit直接把代码

 while (!isStop)
{
isSuccess = !isSuccess;
}

优化成了

 while (true)
{
isSuccess = !isSuccess;
}

要么就是为了加快执行速度,mainthread和task会将isStop变量从memory中加载到各自的cpu缓存中,而主线程执行isStop=true的时候而task读的还是cpu

缓存中的脏数据,也就是还是按照isStop=false的情况进行执行。

四:三种解决方案

1:volatile

那这个问题该怎么解决呢?大家第一个想到的就是volatile关键词,这个关键词我想大家都知道有2个意思:

<1>. 告诉编译器,jit,cpu不要对我进行任何形式的优化,谢谢。

<2>. 该变量必须从memory中读取,而不是cpu cache中。

所以可以将上面的代码优化成如下方式,问题就可以完美解决:

    class Program
{
volatile static bool isStop = false; static void Main(string[] args)
{
var task = Task.Factory.StartNew(() =>
{
var isSuccess = false; while (!isStop)
{
isSuccess = !isSuccess;
}
}); Thread.Sleep();
isStop = true;
task.Wait(); Console.WriteLine("主线程执行结束!");
Console.ReadLine();
}
}

2:Thread.VolatileRead

  这个方法也是.net后来新增的一个方法,它的作用就是告诉CLR,我需要从memory中进行读取,而不是cpu cache中,不信可以看下注释。

         //
// 摘要:
// 读取字段值。无论处理器的数目或处理器缓存的状态如何,该值都是由计算机的任何处理器写入的最新值。
//
// 参数:
// address:
// 要读取的字段。
//
// 返回结果:
// 由任何处理器写入字段的最新值。
public static byte VolatileRead(ref byte address);

不过很遗憾,这吊毛没有bool类型的参数,只有int类型。。。操,,,为了测试只能将isStop改成0,1这两种int状态,哎。。。

     class Program
{
static void Main(string[] args)
{
int isStop = ; var task = Task.Factory.StartNew(() =>
{
var isSuccess = false; while (isStop != )
{
//每次循环都要从内存中读取 ”isStop“ 的最新值
Thread.VolatileRead(ref isStop); isSuccess = !isSuccess;
}
}); Thread.Sleep();
isStop = ;
task.Wait(); Console.WriteLine("主线程执行结束!");
Console.ReadLine();
}
}

3: Thread.MemoryBarrier

  其实这个方法在MSDN上的解释看起来让人觉得莫名奇妙,根本就看不懂。

         //
// 摘要:
// 按如下方式同步内存存取:执行当前线程的处理器在对指令重新排序时,不能采用先执行 System.Threading.Thread.MemoryBarrier
// 调用之后的内存存取,再执行 System.Threading.Thread.MemoryBarrier 调用之前的内存存取的方式。
[SecuritySafeCritical]
public static void MemoryBarrier();

其实这句话大概就两个意思:

<1>. 优化cpu指令排序。

<2>. 调用MemoryBarrier之后,在MemoryBarrier之前的变量写入都要从cache更新到memory中。

调用MemoryBarrier之后,在MemroyBarrier之后的变量读取都要从memory中读取,而不是cpu cache中。

所以基于上面两条策略,我们可以用Thread.MemoryBarrier进行改造,代码如下:

     class Program
{
static void Main(string[] args)
{
bool isStop = false; var task = Task.Factory.StartNew(() =>
{
var isSuccess = false; while (!isStop)
{
Thread.MemoryBarrier();
isSuccess = !isSuccess;
}
}); Thread.Sleep();
isStop = true;
task.Wait(); Console.WriteLine("主线程执行结束!");
Console.ReadLine();
}
}

总结一下,在多线程环境下,多个线程对一个共享变量进行读写是一个很危险的操作,原因我想大家都明白了,这个时候你就可以用到上面三种手段进行解决

啦。。。好了,本篇就说到这里,希望对你有帮助。

享受release版本发布的好处的同时也应该警惕release可能给你引入一些莫名其妙的大bug的更多相关文章

  1. OpenHarmony 3.1 Release版本发布

    OpenHarmony 3.1 Release 版本概述 当前版本在OpenHarmony 3.1 Beta的基础上,更新支持以下能力: 标准系统基础能力增强 本地基础音视频播放能力.视频硬编解码.相 ...

  2. 【我的Android进阶之旅】快速创建和根据不同的版本类型(Dev、Beta、Release)发布Android 开发库到Maven私服

    前言 由于项目越来越多,有很多公共的代码都可以抽取出一个开发库出来传到公司搭建好的Maven私服,以供大家使用. 之前搭建的Maven仓库只有Release和Snapshot两个仓库,最近由于开发库有 ...

  3. KubeEdge 1.12版本发布,稳定性、安全性、可扩展性均带来大幅提升

    摘要:2022年9月29日,KubeEdge发布1.12版本.新版本新增多个增强功能,在扩展性.稳定性.安全性上均有大幅提升. 本文分享自华为云社区<KubeEdge 1.12版本发布,稳定性. ...

  4. xcode 怎么样在发布release版本的时候 不输出log

    我们平时在开发应用的时候,经常会用到 NSLog 来调试我们的程序,而随着项目越来越大,这些用于调试的日志输出就会变得很难管理. 发布正式版的时候一定要屏蔽掉所有后台输出,因为这些输出还是比较消耗系统 ...

  5. 如何使用VC++6.0发布程序(即release版本程序)

    大家都知道VC编译器默认生成debug版本的程序,但是debug版本程序无法运行在没有安装VC的电脑上, 这就要就我们生成release版本的程序,因为release版本在未安装VC的电脑上也能运行( ...

  6. 教你如何使用android studio发布release 版本【转】

    原文链接 想必还有人对如何在Android studio (以下简称as)发布release版本的app而狂刷百度吧?都是过来人,我很理解这种心情,百度到的基本是半成品,为什么这么说呢?百度一下,你就 ...

  7. Android Studio发布Release版本之坑--Unknown host 'd29vzk4ow07wi7.cloudfront.net'

    使用Android Studio发布Release版本时,出现Unknown host 'd29vzk4ow07wi7.cloudfront.net'...错误. 解决方法:修改本机的DNS为8.8. ...

  8. Flutter 发布APK时,release版本和debug版本的默认权限不同

    Flutter 发布APK时,release版本和debug版本的默认权限不同 @author ixenos 在调试模式下,默认情况下启用服务扩展和多个权限(在flutter中) 当您处于发布模式时, ...

  9. Ionic 发布Release 版本

    1.生成releases 版本  cordova build android --release 如果你在生成release版本出错,请修改build.gradle 2.生成签名文件 生成成功后会在G ...

随机推荐

  1. atom编辑器快捷键

    挑来挑去,还是决定选择atom,做为我的编程编辑器. 下面是我总结的atom快捷键 //1.atomcmd+,; 设置cmd+h; 隐藏程序cmd+alt+h; 隐藏其他程序 //2.文件cmd+n; ...

  2. ADO.NET 扩展属性、配置文件 和 对战游戏

    扩展属性 有外键关系时将信息处理成用户可看懂的 利用扩展属性 如:Info表中的民族列显示的是民族代号处理成Nation表中的民族名称 需要在Info类里面扩展一个显示nation名称的属性 例:先前 ...

  3. <C++Primer>第四版 阅读笔记 第二部分 “容器和算法”

    泛型算法中,所谓"泛型(generic)"指的是两个方面:这些算法可作用于各种不同的容器类型,而这些容器又可以容纳多种不同类型的元素. 第九章 顺序容器 顺序容器的元素排列次序与元 ...

  4. Linux驱动技术(七) _内核定时器与延迟工作

    内核定时器 软件上的定时器最终要依靠硬件时钟来实现,简单的说,内核会在时钟中断发生后检测各个注册到内核的定时器是否到期,如果到期,就回调相应的注册函数,将其作为中断底半部来执行.实际上,时钟中断处理程 ...

  5. Swift 实现俄罗斯方块详细思路解析(附完整项目)

    一:写在开发前 俄罗斯方块,是一款我们小时候都玩过的小游戏,我自己也是看着书上的思路,学着用 Swift 来写这个小游戏,在写这个游戏的过程中,除了一些位置的计算,数据模型和理解 Swift 语言之外 ...

  6. Android jni 编程2(对基本类型一维整型数组的操作)

    参考教程和这位博主的对一维数组的处理,主要包括以下三种类型: //传入一维数组,无返回值 public native void arrayEncode(int[] arr); //传一个一维数组和数组 ...

  7. Raspberry树莓派学习笔记2—配置RobotFramework自动化测试环境

    一般RobotFramework都是安装在Windows/Linux的PC机上,这里将简单介绍在树莓派硬件平台上配置RobotFramework的开发和运行环境. 树莓派上配置了自动化测试软件,可以考 ...

  8. gvim生存配置

    set guioptions-=Tcolorscheme desert set clipboard+=unnamedset mouse=a winpos 200 50set lines=20 colu ...

  9. 解决Ubuntu不能连接xshell

    首先,判断Ubuntu是否安装了ssh服务: 1.ps -e |grep ssh 如果服务已经启动,则可以同时看到“ssh-agent”和“sshd”,否则表示没有安装服务,或没有开机启动 2.安装s ...

  10. iOS 快速集成启动页广告

    前言 由于项目中要用到启动页广告,所以做了简单的研究,同时借鉴网易新闻和蘑菇街的交互写了一个简单的demo,现在写出来供大家参考,可能由于个人局限会有一些bug和不完善的地方,也希望大家能够友善提醒和 ...