cmd: Avoid rereading batch file for every call/goto executed (try 2)

Frédéric Delanoy frederic.delanoy at gmail.com
Sun Sep 4 18:42:23 CDT 2011


Added a labels cache.
Current code was parsing the whole file from start for every call/goto, making
it terribly slow to run the cmd test suite.
---
 programs/cmd/builtins.c |  111 +++++++++++++++++++++++++++++++++++++---------
 1 files changed, 89 insertions(+), 22 deletions(-)

diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index bbf2920..901f6a5 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -1342,10 +1342,89 @@ void WCMD_give_help (const WCHAR *command) {
   return;
 }
 
+/*******************************************************************
+ * get_label_position
+ *
+ * Retrieves the position of label in the current batch file.
+ *
+ * PARAMS
+ *  label [I] input label, non NULL or "eof"
+ *  pos   [O] pointer to the file position of label, if found
+ *
+ * RETURNS
+ *  Success: Returns TRUE.
+ *           *pos points to the file position of label, relative to the
+ *           beginning of the file.
+ *  Failure: Returns FALSE.
+ */
+static BOOL get_label_position(const WCHAR *label, LARGE_INTEGER *pos) {
+
+    static BOOL label_cache_initialised = FALSE;
+    static WCHAR **keys = NULL;
+    WCHAR *key;
+    static LARGE_INTEGER *vals = NULL;
+    LARGE_INTEGER val;
+    static SIZE_T elemsCount = 0;
+    static SIZE_T capacity = 8;
+    int i;
+
+    if (!label_cache_initialised) {
+        /* fill the cache with labels */
+        static WCHAR string[MAX_PATH];
+        WCHAR *str;
+
+        keys = HeapAlloc(GetProcessHeap(), 0, capacity * sizeof(WCHAR*));
+        vals = HeapAlloc(GetProcessHeap(), 0, capacity * sizeof(LARGE_INTEGER));
+
+        SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
+        while (WCMD_fgets(string, sizeof(string)/sizeof(WCHAR), context->h)) {
+            str = string;
+            while (isspaceW (*str)) str++;
+            if (*str == ':') {
+                DWORD index = 0;
+                str++;
+                while (str[index] && (!isspaceW(str[index])))
+                    index++;
+                str[index] = '\0';
+
+                if (elemsCount > 0 && (elemsCount == capacity)) {
+                    capacity *= 2;
+                    keys = HeapReAlloc(GetProcessHeap(), 0, keys,
+                                       capacity * sizeof(WCHAR*));
+                    vals = HeapReAlloc(GetProcessHeap(), 0, vals,
+                                       capacity * sizeof(LARGE_INTEGER));
+                }
+
+                key = WCMD_strdupW(str);
+                keys[elemsCount] = key;
+                val.QuadPart = 0;
+                val.u.LowPart = SetFilePointer(context -> h, val.u.LowPart,
+                                              &val.u.HighPart, FILE_CURRENT);
+                vals[elemsCount] = val;
+                elemsCount++;
+            }
+        }
+        label_cache_initialised = TRUE;
+    }
+
+    /* A simple linear search should be quick and simple enough.
+     * Note: if the same label is "defined" twice, only the first one is used,
+     *       as Windows does.
+     */
+    for (i = 0; i < elemsCount; i++) {
+        if (!lstrcmpiW(keys[i], label)) {
+            *pos = vals[i];
+            return TRUE;
+        }
+    }
+
+    return FALSE;
+}
+
 /****************************************************************************
- * WCMD_go_to
+ * WCMD_goto
  *
- * Batch file jump instruction. Not the most efficient algorithm ;-)
+ * Batch file jump instruction.
  * Prints error message if the specified label cannot be found - the file pointer is
  * then at EOF, effectively stopping the batch file.
  * FIXME: DOS is supposed to allow labels with spaces - we don't.
@@ -1353,9 +1432,6 @@ void WCMD_give_help (const WCHAR *command) {
 
 void WCMD_goto (CMD_LIST **cmdList) {
 
-  WCHAR string[MAX_PATH];
-  WCHAR current[MAX_PATH];
-
   /* Do not process any more parts of a processed multipart or multilines command */
   if (cmdList) *cmdList = NULL;
 
@@ -1364,8 +1440,10 @@ void WCMD_goto (CMD_LIST **cmdList) {
     return;
   }
   if (context != NULL) {
-    WCHAR *paramStart = param1, *str;
+    WCHAR *paramStart = param1;
     static const WCHAR eofW[] = {':','e','o','f','\0'};
+    BOOL label_found;
+    LARGE_INTEGER pos;
 
     /* Handle special :EOF label */
     if (lstrcmpiW (eofW, param1) == 0) {
@@ -1376,24 +1454,13 @@ void WCMD_goto (CMD_LIST **cmdList) {
     /* Support goto :label as well as goto label */
     if (*paramStart == ':') paramStart++;
 
-    SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
-    while (WCMD_fgets (string, sizeof(string)/sizeof(WCHAR), context -> h)) {
-      str = string;
-      while (isspaceW (*str)) str++;
-      if (*str == ':') {
-        DWORD index = 0;
-        str++;
-        while (((current[index] = str[index])) && (!isspaceW (current[index])))
-            index++;
-
-        /* ignore space at the end */
-        current[index] = 0;
-        if (lstrcmpiW (current, paramStart) == 0) return;
-      }
+    label_found = get_label_position(paramStart, &pos);
+    if (label_found) {
+        SetFilePointer(context->h, pos.u.LowPart, &pos.u.HighPart, FILE_BEGIN);
+    } else {
+        WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
     }
-    WCMD_output (WCMD_LoadMessage(WCMD_NOTARGET));
   }
-  return;
 }
 
 /*****************************************************************************
-- 
1.7.6




More information about the wine-patches mailing list