在 CoffeeScript 或 JavaScript 中强制内联函数

问题描述 投票:0回答:1

我试图用许多短函数来构建我的代码,因此由于函数和变量的流动,即使没有很多注释,它也很容易阅读。去年,我们用 C 实现了一个高性能应用程序,其结构相同,但为了避免函数调用,我们将几乎每个函数都注释为内联。

现在我正在用 CoffeeScript 编写一个 NodeJS 应用程序,并用数十个从循环调用的小函数来构建我的部分代码。这将聚合到几百到几千个函数调用,具体取决于数据结构的大小。由于这些函数可以合并为一个更大的函数,因此可以避免调用,但这会大大降低代码的质量。

我在 SO 上找到了对 Extreme JavaScript Performance 的引用,这是 2009 年的演示,显示了在 Firefox 和 IE 上内联函数时的巨大性能改进,以及 Chrome 和 Safari 的显着改进。现在已经是 5 年前了,JS 引擎随着时间的推移而不断改进,所以我的问题是:

当 CoffeeScript 创建 JS 或由解析器在 JS 中使用某些特定关键字或参数时,是否可以强制内联函数?

或者我应该等待 V8 引擎自动实现并处理这个问题(或者可能已经完成了)?

javascript node.js coffeescript inline v8
1个回答
0
投票

我已经编写了一个javascript来使用esprima+escodegen进行内联,它仅适用于简单的情况: 示例:

const code = `
    function f(x) {
      return x**2 - 1;
    }
    export function f1(x) {
      return f(x + 1);
    }`;
console.log(inlineFunctionsWrapper(code));

结果:

export function f1(x) {
    const $x = x + 1;
    return $x ** 2 - 1;
}

剧本:

import esprima from 'esprima';
import escodegen from 'escodegen';

const functionsToInline = new Map();

function collectFunctions(node) {
  if (node.type === 'FunctionDeclaration') {
    const name = node.id.name;
    functionsToInline.set(name, node);
  } else if (node.type === 'ClassDeclaration') {
    const className = node.id.name;
    if (node.body.type !== 'ClassBody') {
      for (const member of node.body.body) {
        if (member.type === 'MethodDefinition') {
          const name = member.key.name;
          const fullName = className + '.' + name;
          functionsToInline.set(fullName, member.value);
        }
      }
    }
  } else {
    for (const key in node) {
      const x = node[key];
      if (typeof x === 'object' && x != null) {
        collectFunctions(x);
      }
    }
  }
}

function renameIdentifiers(map, node) {
  if (node.type === 'Identifier') {
    const newNode = map.get(node.name);
    if (newNode != undefined) {
      return newNode;
    }
  } else {
    for (const key in node) {
      const x = node[key];
      if (typeof x === 'object' && x != null) {
        node[key] = renameIdentifiers(map, x);
      }
    }
  }
  return node;
}

function collectIdentifiers(set, node) {
  if (node.type === 'VariableDeclarator') {
    set.add(node.id.name);
  }
  for (const key in node) {
    const x = node[key];
    if (typeof x === 'object' && x != null) {
      collectIdentifiers(set, x);
    }
  }
  return node;
}

function clone(x) {
  return JSON.parse(JSON.stringify(x));
}

function createVariableDeclaration(name, initNode, constant) {
  return {
    "type": "VariableDeclaration",
    "declarations": [
      {
        "type": "VariableDeclarator",
        "id": {
          "type": "Identifier",
          "name": name
        },
        "init": initNode
      }
    ],
    "kind": constant ? "const" : "let"
  };
}

const tmps = new Set();
function getTmpVarName(baseName) {
  if (baseName.startsWith('$')) {
    baseName = baseName.slice(1).replace(/\d+$/g, '');
  }
  let tmpVarName = '$' + baseName;
  let i = 1;
  while (tmps.has(tmpVarName)) {
    tmpVarName = '$' + baseName + '' + i;
    i += 1;
  }
  tmps.add(tmpVarName);
  return tmpVarName;
}

function contains(container, node) {
  if (node === container) {
    return true;
  }
  for (const key in container) {
    const x = container[key];
    if (typeof x === 'object' && x != null) {
      if (contains(x, node)) {
        return true;
      }
    }
  }
  return false;
}

function has(node, type) {
  if (node.type === type) {
    return true;
  }
  for (const key in node) {
    const x = node[key];
    if (typeof x === 'object' && x != null) {
      if (has(x, type)) {
        return true;
      }
    }
  }
  return false;
}

function isConstant(node, name) {
  if (node.type === 'AssignmentExpression' && node.left.type === 'Identifier' && node.left.name === name) {
    return false;
  }
  if (node.type === 'UpdateExpression' && node.argument.type === 'Identifier' && node.argument.name === name) {
    return false;
  }
  for (const key in node) {
    const x = node[key];
    if (typeof x === 'object' && x != null) {
      if (!isConstant(x, name)) {
        return false;
      }
    }
  }
  return true;
}


function inlineFunctions(block, node, exported) {
  if (node.type === 'CallExpression' && (node.callee.type === 'MemberExpression' || node.callee.type === 'Identifier')) {
    const name = node.callee.type === 'MemberExpression' ? node.callee.object.name + '.' + node.callee.property.name : node.callee.name;
    const f = functionsToInline.get(name);
    if (f != null) {
      const paramMap = new Map();
      for (let i = 0; i < f.params.length; i += 1) {
        let argNode = node.arguments[i];
        const param = f.params[i];
        if ((argNode.type !== 'Identifier' || !isConstant(f, param.name)) &&
            (argNode.type !== 'Literal' || !isConstant(f, param.name))) {
          argNode = inlineFunctions(block, argNode, exported);
          let index = 0;
          while (!contains(block.body[index], node)) {
            index += 1;
          }
          const tmpVarName = getTmpVarName(param.name);
          block.body.splice(index, 0, createVariableDeclaration(tmpVarName, argNode, isConstant(f, param.name)));
          argNode = {
            type: 'Identifier',
            name: tmpVarName
          };
        }
        if (param.type !== 'Identifier') {
          throw new Error();
        }
        const name = param.name;
        paramMap.set(name, argNode);
      }
      // rename internal variables:
      const vars = new Set();
      collectIdentifiers(vars, f.body);
      for (const variable of vars) {
        const newName = getTmpVarName(variable);
        paramMap.set(variable, {
          type: 'Identifier',
          name: newName
        });
      }
      
      
      let index = 0;
      while (!contains(block.body[index], node)) {
        index += 1;
      }
      let valid = true;
      if (has(block.body[index], 'AssignmentExpression')) {
        if (block.body[index].type === 'ExpressionStatement' && block.body[index].expression.type === 'AssignmentExpression') {
          if (has(block.body[index].expression.left, 'AssignmentExpression')) {
            valid = false;
          }
          if (has(block.body[index].expression.right, 'AssignmentExpression')) {
            valid = false;
          }
        } else {
          valid = false;
        }
      }
      if (!valid) {
        throw new RangeError('not supported when there is an AssignmentExpression: ' + JSON.stringify(block.body[index]));
      }
      if (has(block.body[index], 'UpdateExpression')) {
        throw new RangeError('not supported when there is an UpdateExpression: ' + JSON.stringify(block.body[index]));
      }
      const parentNode = block.body[index];
      if (contains(parentNode, node)) {
        if (f.body.body[f.body.body.length - 1].type === 'ReturnStatement') {
          for (let i = 0; i < f.body.body.length - 1; i += 1) {
            if (has(f.body.body[i], 'ReturnStatement')) {
              throw new RangeError('not supported when there is extra ReturnStatement');
            }
            block.body.splice(index + i, 0, renameIdentifiers(paramMap, clone(f.body.body[i])));
          }
          return renameIdentifiers(paramMap, clone(f.body.body[f.body.body.length - 1].argument));
        } else {
          throw new Error('return statement should be the last statement');
        }
      }
      console.log(JSON.stringify(block.body, null, 2));
      console.log(JSON.stringify(node, null, 2));
      throw new Error(parentNode.type);
    }
  }
  if (node.type === 'ExportNamedDeclaration' && !exported) {
    return inlineFunctions(block, node, true);
  }
  if (node.type === 'BlockStatement') {
    for (let i = 0; i < node.body.length; i += 1) {
      const x = node.body[i];
      if (typeof x === 'object') {
        inlineFunctions(node, x, exported);
      }
    }
    return node;
  }
  for (const key in node) {
    const x = node[key];
    if (typeof x === 'object' && x != null) {
      node[key] = inlineFunctions(block, x, exported);
    }
  }
  if (node.type === 'FunctionDeclaration') {
    if (!exported) {
      // remove inlined function
      return  {
        type: "EmptyStatement"
      };
    }
  }
  return node;
}

function inlineFunctionsWrapper(code, whiteList = []) {
  let tree = esprima.parse(code, { sourceType: 'module'});
  collectFunctions(tree);
  for (let name of whiteList.values()) {
    functionsToInline.delete(name);
  }
  tree = inlineFunctions(null, tree, false);
  return escodegen.generate(tree);
}

export default inlineFunctionsWrapper;
© www.soinside.com 2019 - 2024. All rights reserved.