Web技术突飞猛进,但是有一个领域一直无法突破----游戏。
游戏的性能要求非常高,一些大型游戏连PC跑起来都很吃力,更不要提在浏览器的沙盒模型里跑了!但是,尽管很困难,许多开发者始终没放弃,希望让浏览器运行3D游戏。
本文就将介绍asm.js和Emscripten的基本用法,介绍如何将C/C++转成JS。
C/C++编译成JS有两个最大的困难。
asm.js就是为了解决这两个问题而设计的:它的变量一律都是静态类型,并且取消垃圾回收机制。除了这两点,它与JavaScript并无差异,也就是说,asm.js是JavaScript的一个严格的子集,只能使用后者的一部分语法。
一旦JavaScript引擎发现运行的是asm.js,就知道这是经过优化的代码,可以跳过语法分析这一步,直接转成汇编语言。另外,浏览器还会调用WebGL通过GPU执行asm.js,即asm.js的执行引擎与普通的JavaScript脚本不同。这些都是asm.js运行较快的原因。据称,asm.js在浏览器里的运行速度,大约是原生代码的50%左右。
下面就依次介绍asm.js的两大语法特点。
再看下面的例子。
函数的参数和返回值,都要用这种方式指定类型。
asm.js没有垃圾回收机制,所有内存操作都由程序员自己控制。asm.js通过TypedArray直接读写内存。
下面就是直接读写内存的例子。
varbuffer=newArrayBuffer(32768);varHEAP8=newInt8Array(buffer);functioncompiledCode(ptr){HEAP[ptr]=12;returnHEAP[ptr+4];}如果涉及到指针,也是一样处理。
size_tstrlen(char*ptr){char*curr=ptr;while(*curr!=0){curr++;}return(curr-ptr);}上面的代码编译成asm.js,就是下面这样。
functionstrlen(ptr){ptr=ptr|0;varcurr=0;curr=ptr;while(MEM8[curr]|0!=0){curr=(curr+1)|0;}return(curr-ptr)|0;}1.4asm.js与WebAssembly的异同如果你对JS比较了解,可能知道还有一种叫做WebAssembly的技术,也能将C/C++转成JS引擎可以运行的代码。那么它与asm.js有何区别呢?
回答是,两者的功能基本一致,就是转出来的代码不一样:asm.js是文本,WebAssembly是二进制字节码,因此运行速度更快、体积更小。从长远来看,WebAssembly的前景更光明。
但是,这并不意味着asm.js肯定会被淘汰,因为它有两个优点:首先,它是文本,人类可读,比较直观;其次,所有浏览器都支持asm.js,不会有兼容性问题。
Emscripten的底层是LLVM编译器,理论上任何可以生成LLVMIR(IntermediateRepresentation)的语言,都可以编译生成asm.js。但是实际上,Emscripten几乎只用于将C/C++代码编译生成asm.js。
首先,新建一个最简单的C++程序hello.cc。
#include $emcchello.cc$nodea.out.jsHelloWorld!上面代码中,emcc命令用于编译源码,默认生成a.out.js。使用Node执行a.out.js,就会在命令行输出HelloWorld。 注意,asm.js默认自动执行main函数。 emcc是Emscripten的编译命令。它的用法非常简单。 #生成a.out.js$emcchello.c#生成hello.js$emcchello.c-ohello.js#生成hello.html和hello.js$emcchello.c-ohello.html三、Emscripten语法3.1C/C++调用JavaScriptEmscripten允许C/C++代码直接调用JavaScript。 新建一个文件example1.cc,写入下面的代码。 #include 然后,将这个程序编译成asm.js。 $emccexample1.cc-oexample1.html浏览器打开example1.html,就会跳出对话框HelloWorld!。 Emscripten允许C/C++代码与JavaScript通信。 新建一个文件example2.cc,写入下面的代码。 #include $emccexample2.cc-oexample2.html浏览器打开网页example2.html,会显示val2==42。 Emscripten提供以下宏。 下面是一个EM_ASM_ARGS的例子。新建文件example3.cc,写入下面的代码。 #include 接着,将这个程序转成asm.js。 $emccexample3.cc-oexample3.html浏览器打开example3.html,会跳出对话框"HellofromC++!"。 JS代码也可以调用C/C++代码。新建一个文件example4.cc,写入下面的代码。 #include Module.cwrap()接受三个参数,含义如下。 除了Module.cwrap(),还有一个Module.ccall()方法,可以在JS代码之中调用C函数。 varresult=Module.ccall('int_sqrt',//C函数的名称'number',//返回值的类型['number'],//参数类型的数组[28]//参数数组);回到前面的示例,现在将example4.cc编译成asm.js。 $emcc-sEXPORTED_FUNCTIONS="['_SquareVal','_main']"example4.cc-oexample4.html注意,编译命令里面要用-sEXPORTED_FUNCTIONS参数给出输出的函数名数组,而且函数名前面加下划线。本例只输出两个C函数,所以要写成['_SquareVal','_main']。 浏览器打开example4.html,就会看到弹出的对话框里面显示下面的内容。 Computing:12.5*12.5=156.253.5C函数输出为JavaScript模块另一种情况是输出C函数,供网页里面的JavaScript脚本调用。新建一个文件example5.cc,写入下面的代码。 extern"C"{doubleSquareVal(doubleval){returnval*val;}}上面代码中,SquareVal是一个C函数,放在extern"C"代码块里面,就可以对外输出。 然后,编译这个函数。 $emcc-sEXPORTED_FUNCTIONS="['_SquareVal']"example5.cc-oexample5.js上面代码中,-sEXPORTED_FUNCTIONS参数告诉编译器,代码里面需要输出的函数名。函数名前面要加下划线。 接着,写一个网页,加载刚刚生成的example5.js。 如果执行环境不是浏览器,而是Node,那么调用C函数就更方便了。新建一个文件example6.c,写入下面的代码。 #include $emcc-sEXPORTED_FUNCTIONS="['_sayHi','_daysInWeek']"example6.c-oexample6.js接着,写一个Node脚本test.js。 varem_module=require('./api_example.js');em_module._sayHi();em_module.ccall("sayHi");console.log(em_module._daysInWeek());上面代码中,Node脚本调用C函数有两种方法,一种是使用下划线函数名调用em_module._sayHi(),另一种使用ccall方法调用em_module.ccall("sayHi")。 运行这个脚本,就可以看到命令行的输出。 另外,由于asm.js的运行速度较快,所以一些计算密集型的操作(比如计算Hash)可以使用C/C++实现,再在JS中调用它们。 (完) 兰宇说: 阮老大好早,半年前作为一个搬运工的我被朋友带入这一行,就是因为魔兽这个游戏,虽然我不是一个游戏开发,但是看到游戏二字立马点进来,mark一下再说 svtter说: 这是打开了js机器学习之路… 邹振忠说: 我以为会顺便说一说王者荣耀的【坏笑】 铃铛说: haobing说: 已经学不过来了,只能静静地看着你们继续前进 许杰学node说: 引用兰宇的发言:这半年多我每天晚上下班都会学到1点之后,必要的话我可以学到半夜三,四点,第二天上班精神还会很好,可是我觉得学的东西太泛,不精,也不基础,所以现在到底是先从泛到精再补基础,还是基础和泛并行,一直到精呢 我现在也是一个新人,觉得基础这东西,得从实际出发,慢慢领悟,不能太操之过急,你每天学习到1,2点,怎么会有这么多精力呢,相比较,我就惭愧多了。。。 RedNax说: 引用许杰学node的发言:我现在也是一个新人,觉得基础这东西,得从实际出发,慢慢领悟,不能太操之过急,你每天学习到1,2点,怎么会有这么多精力呢,相比较,我就惭愧多了。。。 和爱好和习惯有关,我大学时期玩魔兽的时候基本都是晚上2点睡,早上6点半起来继续奋战,因为爱好,投入进去了就不会感觉到累。我之前在工地做过放线的,基本都是每天早上6点起床在太阳下干到晚上11,12点,学徒总是被压榨的,后来做了搬运工,就是扛肥料的,一吨9元,我们一天收入大概在300左右,累了就会喝一瓶冰冻啤酒解乏,所以之前学累了,我会去买一瓶冰冻百威或者雪花和一包花生,边吃边学,每个人都会有他的亢奋点,我之前的亢奋点就是一瓶冰冻脾酒,会让我达到一个最佳状态,集中精神和不会感到疲乏.另外不管再忙,每坐一到2个小时我都会偷偷的到楼梯间爬一下楼梯,做几组俯卧撑,晚上下班的时候也会去快走一小时再做几组深蹲,这可以保持你的肺活量保持你大脑的清醒,午休是必须的,日常午休10分钟到半小时,如果当天你太累,你必须睡够一个多小时。不加班的周末最好去做有氧运动,看你个人的喜好,只要是有氧就行。还有一点,我尝试过白天疯狂学习,晚上到网吧玩游戏到3点回来,这样第二天虽然可以上班,但是效率不是一般的低,所以如果想保持一直精神集中学习的话最好戒掉游戏,否则也不要多玩,希望可以帮到你。 阿飞说: ”运行速度是原生js的50%“,到底是快了还是慢了? rufi说: Rust也可以编译成webasm wZi说: 据称,asm.js在浏览器里的运行速度,大约是原生代码的50%左右。 楼上……原生代码是指nativeC,不是nativejavascript…… StruggleYang说: 瑞尔思说: 3D页游未来估计也是趋势,这边做渲染输出 YXCoder说: 千言万语汇成一句话,峰哥牛B 姜佐腾说: 之前在树莓派上用js操作gpio,因为js速度太慢会错过数据,感觉用这个有点希望了 Simon说: WebGL,three.js估计也会火起来 哥斯拉说: ./emsdkinstallsdk-incoming-64bitbinaryen-master-64bit这一步下载速度实在太慢了,有什么其他方法吗, xxxx说: 引用兰宇的发言:阮老大好早,半年前作为一个搬运工的我被朋友带入这一行,就是因为魔兽这个游戏,虽然我不是一个游戏开发,但是看到游戏二字立马点进来,mark一下再说 你可以寻找一些名气不是特别大,但技术能力还是有的小牛或许更加适合现在的你,也会指导你的。 刚吃了一头牛说: 刚看完再鲁会码9点才上班 杭盖说: 在浏览器的沙盒模型里跑游戏本身就是个伪命题,看了太多的技术消亡都是因为没有产生真正的社会生产力,带不来经济效应。 soyo说: js千秋万代,一统江湖 ccwf2006说: 类型系统是C++的,内存管理是C++的。这是重新发明了一次C++啊。 Huey说: 其实现在WebGL性能上的瓶颈在于JS的运算速度吗?跟浏览器本身是否会有关联?我很想了解的是目前3D在浏览器端性能瓶颈究竟是什么? 李绍伟说: 阮老师终于开始对游戏下手了……期待! 李彦宏说: 浏览器跑些简单的H5应该可以,大型游戏太吃内存,操作系统弱了内存小了都带不上 thc说: 引用哥斯拉的发言:./emsdkinstallsdk-incoming-64bitbinaryen-master-64bit这一步下载速度实在太慢了,有什么其他方法吗, 下载x2d里面有,速度那很快!!! Shepherdog说: 可惜asm.js没有被Safari支持,苹果似乎是一路奔向wasm(iOS11+、Safari11+),或许wasm才是未来的主角,虽然asm.js可读,可以转译成wasm,但应该没人真去读这些代码吧,可读性好不到哪里去,更多场景是开发者不希望别人看到自己的明文代码。而wasm是优化JIT阶段的字节码,类似于smali,更像二进制的机器码,更安全的分发,比asm.js更高的压缩比和执行效率,既生瑜何生亮乎? liu说: 为什么我测的只有IE10对asm.js兼容,IE其他版本都不兼容。 大名说: 搞js的得累死,新东西多如牛毛。es6都还没掌握。 JasonHuang说: 言心说: ./emsdkinstall--build=Releasesdk-incoming-64bitbinaryen-master-64bit 请教一下,在window下怎么安装? wurenzidi说: "另外,浏览器还会调用WebGL通过GPU执行asm.js,即asm.js的执行引擎与普通的JavaScript脚本不同。这些都是asm.js运行较快的原因。"——————————————————————————————————————————SeriouslyGPU执行ams.js chai2010说: 欢迎提供建议 morris说: 熊猫小侠说: 我从网上下了一个C语言的库,希望可以编译成asm.js,但现在只生成wasm,老大指教一下原因?下面是编译命令 #!/bin/bashemcccrypto/*.c-Oz\-sDISABLE_EXCEPTION_CATCHING=1\-sBINARYEN_ASYNC_COMPILATION=1\-sALIASING_FUNCTION_POINTERS=1\-sALLOW_MEMORY_GROWTH=1\-sWASM=1\-sBINARYEN=1\-sNO_EXIT_RUNTIME=1\-sASSERTIONS=1\-sSTACK_OVERFLOW_CHECK=1\-sEXPORTED_FUNCTIONS="['_cryptonight_hash']"\--post-js../web/lib/worker.js\-o../web/lib/cryptonight.js jupiterben说: -sWASM=1改成0 翟明说: 不知道这个东西有没有javaapplet快,好多年前的技术了 killinux说: linwei说: 皮卡丘说: 引用阿飞的发言:”运行速度是原生js的50%“,到底是快了还是慢了? 不是原生js的50%,是c,c++,rust这些语言的50%。 hh说: 如何在react中调用生成的.js 李伟说: 111说: "一旦JavaScript引擎发现运行的是asm.js,就知道这是经过优化的代码,可以跳过语法分析这一步,直接转成汇编语言。另外,浏览器还会调用WebGL通过GPU执行asm.js,即asm.js的执行引擎与普通的JavaScript脚本不同。这些都是asm.js运行较快的原因" 这是认真的???跳过语法分析???GPU运行JS??? 马里奥说: 大家都怎么样了,我来晚了,研究wasm来了 kui-kui说: 还有更高阶的演示吗?最近在看embind但是网上的例子都说的只言片语的执行不成功TestFile