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

Frédéric Delanoy frederic.delanoy at gmail.com
Thu Oct 6 18:23:18 CDT 2011


Added a labels/file position cache in "non-label" (i.e. file) batch contexts.
The cache is shared between all labels belonging to the same batch file.
For simplicity, the whole cache is built when the first label is encountered.

Current code was parsing the whole file from start for every call/goto looking for the label, making cmd terribly slow for batch files with lots of labels/subroutine calls.
---
 programs/cmd/batch.c    |   17 +++++
 programs/cmd/builtins.c |  149 ++++++++++++++++++++++++++++++++++++++++-------
 programs/cmd/wcmd.h     |   10 +++
 3 files changed, 154 insertions(+), 22 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index a276d3e..5a8e6ce 100644
--- a/programs/cmd/batch.c
+++ b/programs/cmd/batch.c
@@ -48,6 +48,7 @@ void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HAN
 
   HANDLE h = INVALID_HANDLE_VALUE;
   BATCH_CONTEXT *prev_context;
+  SIZE_T i;
 
   if (startLabel == NULL) {
     h = CreateFileW (file, GENERIC_READ, FILE_SHARE_READ|FILE_SHARE_WRITE|FILE_SHARE_DELETE,
@@ -73,6 +74,14 @@ void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HAN
   context->batchfileW = WCMD_strdupW(file);
   context -> command = command;
   memset(context -> shift_count, 0x00, sizeof(context -> shift_count));
+  if (startLabel)
+    context->labels = NULL;
+  else {
+    context->labels = HeapAlloc(GetProcessHeap(), 0, sizeof(CMD_LABELS));
+    context->labels->names = 0;
+    context->labels->positions = 0;
+    context->labels->count = 0;
+  }
   context -> prev_context = prev_context;
   context -> skip_rest = FALSE;
 
@@ -103,6 +112,14 @@ void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HAN
  */
 
   HeapFree(GetProcessHeap(), 0, context->batchfileW);
+  if (context->labels) {
+    for (i=0; i < context->labels->count; i++) {
+      HeapFree(GetProcessHeap(), 0, context->labels->names[i]);
+    }
+    HeapFree(GetProcessHeap(), 0, context->labels->names);
+    HeapFree(GetProcessHeap(), 0, context->labels->positions);
+    HeapFree(GetProcessHeap(), 0, context->labels);
+  }
   LocalFree (context);
   if ((prev_context != NULL) && (!called)) {
     prev_context -> skip_rest = TRUE;
diff --git a/programs/cmd/builtins.c b/programs/cmd/builtins.c
index 6b4d0ab..078293b 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -1357,10 +1357,127 @@ void WCMD_give_help (const WCHAR *command) {
   return;
 }
 
+/*
+ * Builds the label cache for a given batch context (with no initialized label cache).
+ * Returns TRUE on success, FALSE otherwise
+ */
+static BOOL build_label_cache_context(BATCH_CONTEXT *context)
+{
+    WCHAR string[MAXSTRING];
+    WCHAR *str;
+    SIZE_T elems_count = 0, capacity = 8;
+    WCHAR **keys = HeapAlloc(GetProcessHeap(), 0, capacity * sizeof(*keys));
+    LARGE_INTEGER *vals = HeapAlloc(GetProcessHeap(), 0, capacity * sizeof(*vals));
+
+    if (!keys || !vals)
+        goto err;
+
+    SetFilePointer (context -> h, 0, NULL, FILE_BEGIN);
+    while (WCMD_fgets(string, sizeof(string)/sizeof(WCHAR), context->h)) {
+        WCHAR *key, *end;
+        LARGE_INTEGER val;
+
+        str = string;
+        while (isspaceW (*str)) str++;
+        if (*str != ':') continue;
+
+        for (end = ++str; *end && !isspaceW(*end); end++) ;
+        *end = '\0';
+
+        if (elems_count == capacity) {
+            WCHAR **_keys;
+            LARGE_INTEGER *_vals;
+            capacity *= 2;
+            if (!(_keys = HeapReAlloc(GetProcessHeap(), 0, keys, capacity * sizeof(*keys))))
+                goto err;
+            keys = _keys;
+            if (!(_vals = HeapReAlloc(GetProcessHeap(), 0, vals, capacity * sizeof(*vals))))
+                goto err;
+            vals = _vals;
+        }
+
+        if (!(key = WCMD_strdupW(str)))
+            goto err;
+        keys[elems_count] = key;
+        val.u.HighPart = 0;
+        val.u.LowPart = SetFilePointer(context -> h, 0, &val.u.HighPart, FILE_CURRENT);
+        vals[elems_count] = val;
+        elems_count++;
+    }
+    context->labels->names = keys;
+    context->labels->positions = vals;
+    context->labels->count = elems_count;
+
+    return TRUE;
+
+err:
+    HeapFree(GetProcessHeap(), 0, keys);
+    HeapFree(GetProcessHeap(), 0, vals);
+    WINE_ERR("Unable to allocate memory for labels cache!\n");
+    return FALSE;
+}
+
+/*
+ * Returns batch context containing the label cache associated to a given
+ * batch context, or NULL on error
+ */
+static BATCH_CONTEXT *get_label_cache_context(BATCH_CONTEXT *context)
+{
+    BATCH_CONTEXT *cache_context;
+
+    /* Locate (and possibly create) nearest cache, i.e. that of first non-label
+     * context ancestor (or self) */
+    cache_context = context;
+    while (!cache_context->labels)
+        cache_context = cache_context->prev_context;
+
+    if (cache_context->labels->names)
+        return cache_context;
+
+    return build_label_cache_context(cache_context) ? cache_context : NULL;
+}
+
+/*******************************************************************
+ * 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)
+{
+    SIZE_T i;
+    BATCH_CONTEXT *cache_context;
+
+    if (!(cache_context = get_label_cache_context(context)))
+        return FALSE;
+
+    /* 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 < cache_context->labels->count; i++) {
+        if (!lstrcmpiW(cache_context->labels->names[i], label)) {
+            *pos = cache_context->labels->positions[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.
@@ -1368,15 +1485,14 @@ 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;
 
   if (context != NULL) {
-    WCHAR *paramStart = param1, *str;
+    WCHAR *paramStart = param1;
     static const WCHAR eofW[] = {':','e','o','f','\0'};
+    BOOL label_found;
+    LARGE_INTEGER pos;
 
     if (param1[0] == 0x00) {
       WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOARG));
@@ -1392,24 +1508,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_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
     }
-    WCMD_output_stderr(WCMD_LoadMessage(WCMD_NOTARGET));
   }
-  return;
 }
 
 /*****************************************************************************
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h
index 61cef8f..8c9a6b5 100644
--- a/programs/cmd/wcmd.h
+++ b/programs/cmd/wcmd.h
@@ -122,6 +122,14 @@ void      WCMD_execute (const WCHAR *orig_command, const WCHAR *redirects,
                         const WCHAR *parameter, const WCHAR *substitution,
                         CMD_LIST **cmdList);
 
+/* Data structure to hold label/file position cache */
+
+typedef struct {
+    WCHAR **names;            /* List of labels, in file order */
+    LARGE_INTEGER *positions; /* Associated file offsets */
+    SIZE_T count;             /* Number of (labels/pos) stored */
+} CMD_LABELS;
+
 /* Data structure to hold context when executing batch files */
 
 typedef struct _BATCH_CONTEXT {
@@ -129,6 +137,8 @@ typedef struct _BATCH_CONTEXT {
   HANDLE h;             /* Handle to the open batch file */
   WCHAR *batchfileW;    /* Name of same */
   int shift_count[10];	/* Offset in terms of shifts for %0 - %9 */
+  CMD_LABELS *labels;   /* Labels and associated file positions (offsets) from beginning of the
+                           file for non-label (i.e. file) contexts, or NULL for label contexts */
   struct _BATCH_CONTEXT *prev_context; /* Pointer to the previous context block */
   BOOL  skip_rest;      /* Skip the rest of the batch program and exit */
   CMD_LIST *toExecute;  /* Commands left to be executed */
-- 
1.7.7




More information about the wine-patches mailing list