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

Frédéric Delanoy frederic.delanoy at gmail.com
Mon Sep 19 18:10:05 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 test suite.

try 4: Use MAXSTRING instead of MAX_PATH as WCHAR array size in build_label_cache_context function
try 3: code cleanup/factorisation
try 2: store labels cache in batch context
---
 programs/cmd/batch.c    |   11 ++++
 programs/cmd/builtins.c |  149 ++++++++++++++++++++++++++++++++++++++++-------
 programs/cmd/wcmd.h     |   10 +++
 3 files changed, 148 insertions(+), 22 deletions(-)

diff --git a/programs/cmd/batch.c b/programs/cmd/batch.c
index f988d95..0432b9a 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,9 @@ 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));
+  context->labels.names = 0;
+  context->labels.positions = 0;
+  context->labels.count = 0;
   context -> prev_context = prev_context;
   context -> skip_rest = FALSE;
 
@@ -103,6 +107,13 @@ void WCMD_batch (WCHAR *file, WCHAR *command, int called, WCHAR *startLabel, HAN
  */
 
   HeapFree(GetProcessHeap(), 0, context->batchfileW);
+  if (context->labels.names) {
+    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);
+  }
   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 99a2314..da0dac6 100644
--- a/programs/cmd/builtins.c
+++ b/programs/cmd/builtins.c
@@ -1358,10 +1358,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->command == ':')
+        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.
@@ -1369,15 +1486,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 (WCMD_LoadMessage(WCMD_NOARG));
@@ -1393,24 +1509,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;
 }
 
 /*****************************************************************************
diff --git a/programs/cmd/wcmd.h b/programs/cmd/wcmd.h
index b7bc175..468871e 100644
--- a/programs/cmd/wcmd.h
+++ b/programs/cmd/wcmd.h
@@ -118,6 +118,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 {
@@ -125,6 +133,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 */
   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.6.3




More information about the wine-patches mailing list