无第三方库时加速JS数组运算:Connect4 AI启发函数优化求助
我在计算机科学课程中,基于Keith Galli的Python版Connect4 AI实现了JavaScript版本,但启发函数经优化后仍比原版慢(7-11ms对比<1ms)。核心差异是原版使用numpy进行多维数组运算,而我处于code.org AppLab工作环境,无法导入/安装任何第三方库。
以下是我用于评估位置优势的启发函数代码:
function score_position(board, piece){ var start = Date.now(); if (!score_position.cache) { score_position.cache = {}; } var key = board.join("") + piece; if (score_position.cache.hasOwnProperty(key)) { var end = Date.now(); console.log("Elapsed time (cached): " + (end - start) + "ms"); return score_position.cache[key]; } var score = 0; var center_count = 0; for(var i=0; i < board.length; i++){ if (board[i][3] === piece) { center_count++; } } score += center_count * 3; var col_array = []; var diag1_array = []; var diag2_array = []; for(var r=0; r<ROW_COUNT; r++){ for(var c=0; c<COLUMN_COUNT-3; c++){ var win = board[r].slice(c, c+WINDOW_LENGTH); if (win.indexOf('') === -1) { score += evaluate_window(win, piece); } } } for(var r=0; r<ROW_COUNT; r++){ for(var c=0; c<COLUMN_COUNT; c++){ col_array[c] = board[r][c]; } for(var ct=0; ct<ROW_COUNT-3; ct++){ var win = col_array.slice(ct, ct+WINDOW_LENGTH); if (win.indexOf('') === -1) { score += evaluate_window(win, piece); } } } for(var r=0; r<ROW_COUNT-3; r++){ for(var c=0; c<COLUMN_COUNT-3; c++){ diag1_array[0] = board[r][c]; diag1_array[1] = board[r+1][c+1]; diag1_array[2] = board[r+2][c+2]; diag1_array[3] = board[r+3][c+3]; if(diag1_array.indexOf('') === -1) { score += evaluate_window(diag1_array, piece); } } } for(var r=0; r<ROW_COUNT-3; r++){ for(var c=0; c<COLUMN_COUNT-3; c++){ diag2_array[0] = board[r+3][c]; diag2_array[1] = board[r+2][c+1]; diag2_array[2] = board[r+1][c+2]; diag2_array[3] = board[r][c+3]; if(diag2_array.indexOf('') === -1) { score += evaluate_window(diag2_array, piece); } } } score_position.cache[key] = score; var end = Date.now(); console.log("Elapsed time:" + (end-start) + "ms"); return score; }
该函数遍历棋盘的行、列和对角线,调用evaluate_window函数为其中的窗口评分:
function evaluate_window(win, piece){ if(!evaluate_window.cache){ evaluate_window.cache = {}; } var key = win.join("") + piece; if (evaluate_window.cache.hasOwnProperty(key)) { return evaluate_window.cache[key]; } var score = 0; var opp_piece = 1; if(piece == 1){ opp_piece = 2; } if(win.count(piece) == 4){ score += 100; } else if (win.count(piece) == 3 && win.count(0) == 1){ score += 5; } else if (win.count(piece) == 2 && win.count(0) == 2){ score += 2; } if (win.count(opp_piece) == 3 && win.count(0) == 1){ score -= 4; } evaluate_window.cache[key] = score; return score; }
我已在两个函数中使用记忆化缓存来减少重复状态的计算,同时会跳过空窗口的循环,但代码运行仍较慢,推测是数组运算的开销问题。请问在无第三方库的情况下,如何优化这段代码以提升运行速度?
优化方案
1. 避免数组切片与新数组创建
当前代码频繁使用slice生成新窗口数组,带来内存分配和拷贝开销。直接在原棋盘上遍历窗口内的位置并统计棋子数量,无需生成新数组,能大幅减少性能损耗。
2. 内联窗口评分逻辑
移除evaluate_window函数,将其评分逻辑直接整合到score_position中,消除函数调用和缓存字典查找的额外开销(小窗口的缓存收益远低于这些开销)。
3. 优化缓存键生成
原缓存键使用board.join("")会生成包含逗号、方括号的冗余字符串,改为将棋盘转为紧凑的一维字符串(如每一行的数字直接拼接),缩短键长度,加快字典查找速度。
4. 移除不必要的空窗口检查
原代码跳过空窗口的逻辑与原版numpy实现不符,且额外增加了数组遍历开销。直接处理所有窗口,在计数时自然区分空位情况,同时保留原有评分规则。
5. 替换自定义计数方法
替换win.count(piece)这类自定义扩展方法,直接在窗口遍历过程中统计我方棋子、对手棋子和空位的数量,避免额外函数调用。
优化后示例代码
function score_position(board, piece) { var start = Date.now(); if (!score_position.cache) { score_position.cache = {}; } // 生成紧凑缓存键:将二维棋盘转为无冗余字符的一维字符串 var boardKey = ''; for (var r = 0; r < ROW_COUNT; r++) { boardKey += board[r].join(''); } var key = boardKey + piece; if (score_position.cache.hasOwnProperty(key)) { var end = Date.now(); console.log("Elapsed time (cached): " + (end - start) + "ms"); return score_position.cache[key]; } var score = 0; var opp_piece = piece === 1 ? 2 : 1; // 计算中心列分数 var center_count = 0; for (var r = 0; r < ROW_COUNT; r++) { if (board[r][3] === piece) center_count++; } score += center_count * 3; // 处理行窗口 for (var r = 0; r < ROW_COUNT; r++) { var row = board[r]; for (var c = 0; c <= COLUMN_COUNT - WINDOW_LENGTH; c++) { var own = 0, opp = 0, empty = 0; // 遍历窗口内的4个位置统计数量 for (var i = 0; i < WINDOW_LENGTH; i++) { var val = row[c + i]; if (val === piece) own++; else if (val === opp_piece) opp++; else empty++; } // 直接计算当前窗口的分数 if (own === 4) score += 100; else if (own === 3 && empty === 1) score += 5; else if (own === 2 && empty === 2) score += 2; if (opp === 3 && empty === 1) score -= 4; } } // 处理列窗口 for (var c = 0; c < COLUMN_COUNT; c++) { for (var r = 0; r <= ROW_COUNT - WINDOW_LENGTH; r++) { var own = 0, opp = 0, empty = 0; for (var i = 0; i < WINDOW_LENGTH; i++) { var val = board[r + i][c]; if (val === piece) own++; else if (val === opp_piece) opp++; else empty++; } if (own === 4) score += 100; else if (own === 3 && empty === 1) score += 5; else if (own === 2 && empty === 2) score += 2; if (opp === 3 && empty === 1) score -= 4; } } // 处理正对角线(左上到右下) for (var r = 0; r <= ROW_COUNT - WINDOW_LENGTH; r++) { for (var c = 0; c <= COLUMN_COUNT - WINDOW_LENGTH; c++) { var own = 0, opp = 0, empty = 0; for (var i = 0; i < WINDOW_LENGTH; i++) { var val = board[r + i][c + i]; if (val === piece) own++; else if (val === opp_piece) opp++; else empty++; } if (own === 4) score += 100; else if (own === 3 && empty === 1) score += 5; else if (own === 2 && empty === 2) score += 2; if (opp === 3 && empty === 1) score -= 4; } } // 处理反对角线(右上到左下) for (var r = WINDOW_LENGTH - 1; r < ROW_COUNT; r++) { for (var c = 0; c <= COLUMN_COUNT - WINDOW_LENGTH; c++) { var own = 0, opp = 0, empty = 0; for (var i = 0; i < WINDOW_LENGTH; i++) { var val = board[r - i][c + i]; if (val === piece) own++; else if (val === opp_piece) opp++; else empty++; } if (own === 4) score += 100; else if (own === 3 && empty === 1) score += 5; else if (own === 2 && empty === 2) score += 2; if (opp === 3 && empty === 1) score -= 4; } } score_position.cache[key] = score; var end = Date.now(); console.log("Elapsed time:" + (end - start) + "ms"); return score; }
内容的提问来源于stack exchange,提问作者Salban Nithilaselvan

