`
RednaxelaFX
  • 浏览: 3049396 次
  • 性别: Icon_minigender_1
  • 来自: 海外
社区版块
存档分类
最新评论

迷思:运行时有没有办法消除这样的冗余?

阅读更多
正好刚才在看别的问题的时候看到了这么一个过程,而这显然不是什么特例而是存在于许多地方的代码。记下来看看。

假如要使用Java来调用系统的某个命令或者程序,则很有可能会用到java.lang.Runtime类的exec系列的方法。这里假设我们要使用的重载是Runtime.exec(String[] cmdarray)。
再假如需要传入命令的参数是从一个长度未知的来源来的,就假设来源是一个Iterable<String>吧。

为了组装出Runtime.exec()方法需要的数组,首先把Iterable<String>的内容都装到一个ArrayList<String>里:
// suppose the source is:
// Iterable<String> src;
ArrayList<String> paramList = new ArrayList<String>();
for (String s : src)
    paramList.add(s);
String[] params = paramList.toArray(new String[paramList.size()]);

这一步中,我们至少创建了一个ArrayList和一个String数组。ArrayList在执行add()的时候内部也有可能创建新数组;由于事先不知道长度,所以无法在构造ArrayList的时候预设长度,不过这个就暂时不管了。

接下来,用这个数组来调用exec():
Runtime.getRuntime().exec(params);

然后看看里面发生了些什么事情。

Runtime.exec(String[])调用了Runtime.exec(String[], String[], File);后者的实现是:
public Process exec(String[] cmdarray, String[] envp, File dir) throws IOException {
    return new ProcessBuilder(cmdarray)
        .environment(envp)
        .directory(dir)
        .start();
}


OK,那么ProcessBuilder的构造器里又做了什么呢?
public ProcessBuilder(String... command) {
    this.command = new ArrayList<String>(command.length);
    for (String arg : command)
        this.command.add(arg);
}

它把输入进来的String数组转成了一个新创建的ArrayList<String>。

然后在执行到ProcessBuilder.start()的时候呢?
public Process start() throws IOException {
    // Must convert to array first -- a malicious user-supplied
    // list might try to circumvent the security check.
    String[] cmdarray = command.toArray(new String[command.size()]);
    for (String arg : cmdarray)
        if (arg == null) throw new NullPointerException();
    // Throws IndexOutOfBoundsException if command is empty
    String prog = cmdarray[0];

    SecurityManager security = System.getSecurityManager();
    if (security != null)
        security.checkExec(prog);

    String dir = directory == null ? null : directory.toString();

    try {
        return ProcessImpl.start(cmdarray,
            environment,
            dir,
            redirectErrorStream);
    } catch (IOException e) {
        // It's much easier for us to create a high-quality error
        // message than the low-level C code which found the problem.
        throw new IOException(
            "Cannot run program \"" + prog + "\""
            + (dir == null ? "" : " (in directory \"" + dir + "\")")
            + ": " + e.getMessage(),
        e);
    }
}

注意到这个方法一开始就把先前创建的ArrayList<String>给转换回到了String数组。当然,这个数组又是新创建的。

这个ProcessBuilder.start()方法里又调用了ProcessImpl.start(),其中又再调用了ProcessImpl的构造器;这个构造器中则将刚才来回折腾了多次的参数列拼接为一个大的字符串。

整理一下,刚才经历的过程是:
(来源) ->
ArrayList<String> -> String[] // 我们写的程序
String[] -> ArrayList<String> // ProcessBuilder的构造器
ArrayList<String> -> String[] // ProcessBuilder.start()
String[] -> StringBuilder -> String // ProcessImpl的相关代码

天啊,真是折腾。从现有的JVM的实现看,寿命非常短的对象在垃圾收集时的开销并不很大,创建对象的开销相对来说有可能更大些。(分配空间非常快,开销很小;创建对象的开销主要来自对象的初始化,这部分是用户代码决定的)。
即便如此,明明可以一口气把一个数组用到底的地方被这么来回倒腾显然对运行效率不会有正面影响。
郁闷的是,这些操作都太“详细”了;为了保持代码的语义的正确性,运行时能够做的优化就受到了限制——除非,对这些可以称得上“pattern”的情况做特殊处理,例如说:
看到这样的模式:
// suppose the source is:
// T[] array1;
ArrayList<T> list = new ArrayList<T>();
for (T t : array1)
    list.add(t);
T[] array2 = list.toArray(new T[list.size()]);
// do something with array2 in following code

通过SSA之类的方式确认这些相关变量在中间都没有涉及被别的来源赋值,并且list没有被别的地方使用的话,就去掉整个list的构造和赋值等操作。由于JVM可以积极的将方法调用转换为内联的形式,许多时候方法调用链中原本是不同方法中的局部变量就能变成同一个方法的局部变量,从而允许上述优化的实施。

但通过这种模式识别来做就得预先确定很多有代表性的模式才有用。这显然不算是什么好办法。可是还有什么别的、更通用而又能保证程序语义的正确性的办法呢?Hmm……
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics