在我加入Eigenharp仪器后,今后告别了Java开发。新工做须要开发跨平台的实时音频软件,要求处理大量数据输出和低延迟。开发工做基本上使用C++完成,用到了Juce函数库和用CPython编写的一些胶水代码(glue code)。我被安排开发音乐演奏软件。在作技术选型时,我意识到了那些在Java世界里习觉得常的事情(其实不那么简单)。html
但有哪些事情让Java变得很棒?列出Java很棒的10个缘由并不困难。你能够已经注意到了其中的一些,然而在开发选型时大声说出Java的好处是颇有技巧的。下面是我钟爱Java的几个理由:java
咱们从Java编译器开始,后面的几点会在接下来的文章中详细讨论。python
Java编译器的优异之处shell
Java编译器多是做为开发者遇到的第一个平台组件。在开始学Java语言时,须要用它来编译你的“Hello World”程序,经过它将你的源代码转为可执行程序。macos
没有字节码(bytecode)就没有Java编译器。除了字节码自己的优势(后面的文章会有讨论),这种中间形式还支持运行时JIT(即时编译)。性能优化
JVM的即时编译所向无敌微信
你可能认为JIT只是一种性能改进,让解析器的运行速度匹配本地程序。但实际上,JIT的速度比本地程序更快。若是像C++那样直接编译成本地程序,须要在编译时进行静态优化(static optimization)。这种作法过早地考虑了性能优化,与在发布时带有运行时监控功能的作法截然不同。提早优化改变了不少指令,这与你在源代码中表达的内容造成了差距,程序的实际执行结果可能会与代码中的逻辑有所差异。架构
遇到这种状况,一般的作法是人为调整优化级别,指望以此获得能够正确运行的程序。在深刻编译器细节以前,一般没法知道分析器(profiler)会对代码的执行产生怎样的影响,当前的优化级别是否正确。有时惟一可行的作法是经过变动集合(change set)及时回退到以前的工做,此时你的代码与验证过的问题之间已经再也不有任何联系。随着项目日益庞大,一般优化级别会再也不那么激进,这种状况一般没法找出更高的优化级别为何会引入问题。模块化
优化开关的“黑盒”特性使得这种状况越发糟糕。下面是你在clang手册里能够找到的全部内容,但愿你能从中幸运地发现有用的信息:函数
代码生成选项
-O0 -O1 -O2 -Os -Oz -O3 -Ofast -O4
请指定须要使用的优化级别
-O0 表示“没有优化”:该级别编译速度最快,而且生成的代码可调式性最佳。
-O2是中间级别的优化,能够生成最优化的代码。
-Os与-O2相似,能够额外减小代码大小。
-Oz与-Os(以及-O2)相似,可是代码大小会进一步减少。
-O3与-O2相似,可是编译过程稍长生成的代码大小也稍大(这样可使得代码执行速度更快)。
-Ofast具有全部-O3优化功能,可是因为优化程度过大可能与一些语言标准不兼容。
在某些平台下,-O4会开启连接时优化(link-time optimization);对象文件会以LLVM bitcode文件格式存储,全部优化在连接时执行。
-O1优化位于-O0和-O2之间。
专一你的代码,而不是编译器结构
因为Java编译器的惟一工做是将源代码转为字节码,使用javac命令变得很是简单。一般你全部须要关心的就是,设置正确的classpath信息、选择兼容的VM版本、确认class文件存储的位置,全部的3个编译选项就是: -classpath、-target和-d。
C++的选项更多也更复杂。下面展现了相对简单的一个g++编译器调用。里面包括了一些项目相关的flag以及头文件-I flag,相似javac的classpath。这些都是C++的标准习惯,一般也是进行模块化和提供平台独立构建的惟一办法。
1
2
3
4
5
6
7
8
9
|
g++-4.2 -o tmp
/obj/eigend-gpl/piagent/src/pia_buffer
.os -c -arch i386
-DDEBUG_DATA_ATOMICITY_DISABLED
-DPI_PREFIX=\"
/usr/pi/Python
.framework
/Versions/2
.5\"
-mmacosx-version-min=10.6 -ggdb -Werror -Wall -Wno-deprecated-declarations
-Wno-
format
-O4 -fmessage-length=0 -falign-loops=16 -msse3 -DALIGN_16
-DBUILDING_PIA -fvisibility=hidden -fPIC -Isteinberg -Ieigend-gpl
/steinberg
-Ieigend-gpl -I. -I
/usr/pi/Python
.framework
/Versions/2
.5
/include/python2
.5
-Itmp
/exp
eigend-gpl
/piagent/src/pia_buffer
.cpp
|
最大的问题在于每一个编译器都有不一样的选项。G++与Clang不一样,与C++编译器也不一样,此外还有Visual Studio C++编译器等等。对经常使用的命令行参数,这些编译器使用不一样的命名,各自支持不一样版本的C++标准或标准的不一样子集。此外,它们还提供特定编译选项。
若是指望得到最佳性能,你须要从每一个编译器的数百个编译选项中艰难地搜寻,有时还须要具有目标硬件平台的底层知识。更糟糕的是,你须要提早作好准备指望编译选项能够正常运行在全部平台或处理器架构上,不然当程序运行失败后没有任何办法进行追踪。每次发布我都提心吊胆,由于找出客户报告中软件崩溃的缘由是个人主要职责。有几回,咱们不得不在同一个处理器上不断调整编译器选项,重现问题并生成稳定的二进制。
没有静态连接,也没有动态连接,只有运行时连接
当你生成本地二进制时,你能够选择如何连接本身的二进制或者模块。静态连接将全部内容打包到一个可执行文件里,这样没法独立更新函数库,也不能生成更大的二进制文件。运行时连接更容易发布,并且没有动态连接的上述问题。在实际开发中,一个大型产品单独发布静态连接生成的可执行程序是不现实的。即便你将构建拆分红多个模块或者调用第三方函数库,早晚仍是须要面对动态连接问题。
动态连接很是复杂并且会引入不少麻烦。除了代码内部的可见性(private、public等),你须要单独编译本身的API,确保建立的动态连接库导出正确的符号(symbol)。另外一方面,你须要为实际使用函数库的代码导入这些符号。若是使用了同一个函数库的头文件和客户端,须要在所处的编译环境下为你的API声明须要传入的正确参数。除此以外,在MacOSX、Linux、Windows上动态连接也有着不一样的含义。你须要整理代码,为不一样的平台使用不一样的宏定义(macro)。固然,可能还须要维护一个通用的代码库(codebase)以支持全部平台。立刻你就会发现,实际的编译过程当中编译器会将连接应用到代码的每一个声明,有时你最终使用的class结构会由编译器规定。
即便一切就绪,还须要编译不少共享函数库——尤为是在Windows平台上,每一个用户都须要面对DLL版本不兼容问题。尽管在新版本的Windows上这个问题有所改善,但你的二进制仍是绑定到了特定版本的DLL,一个看似很小的API变化都会让动态连接再也不兼容。要解决这个问题,能够将这些DLL做为应用程序的私有绑定,或者在运行时动态加载。若是使用动态加载,须要格外注意在应用程序内部进行链接和符号解析。而实际上你不该该关心这些细节。
Java经过运行时连接绕过了全部这些问题。全部内容都是动态的,经过字节码导出的符号与包没有直接关联,基本上不须要关心连接过程。若是确实有须要,你还能够动态地加载类或方法,可是这种状况不多出现。字节码的符号表示是很是稳定的,即便class的版本不断演变以前编写的代码仍是能够直接运行,除非函数库的做者故意修改API。
接下来
这是Java 10大有点第一部分。请留言或者经过tweet @gbevin联系我。下一篇会讨论Core API,相信会给你留下更深的映像。不要换台哦!
-- 扫描加关注,微信号: importnew --
原文连接: zeroturnaround 翻译: ImportNew.com - 唐尤华
译文连接: http://www.importnew.com/6268.html
[ 转载请保留原文出处、译者、译文连接和上面的微信二维码图片。]