improve mgetl performance 34/19434/9
Antoine ELIAS [Thu, 28 Sep 2017 11:43:14 +0000 (13:43 +0200)]
Change-Id: I173fe1da5222cf26028aa95dbac85806a2e7c953

scilab/CHANGES.md
scilab/modules/fileio/src/cpp/mgetl.cpp
scilab/modules/fileio/tests/benchmarks/mgetl_verylongfile.tst [new file with mode: 0644]

index 4e8b862..a497513 100644 (file)
@@ -226,7 +226,7 @@ bounds selected out of the axes areas is now restored, after the Scilab 5.4 regr
   - For unregistered user-defined macros, `"script"` is now returned instead of `[]`.
 * `evstr` now support inputs including `ascii(10)`, `ascii(13)`, only a comment or/and
    spaces or/and `"[]"`, only spaces, being `""`, or instructions returning nothing.
-
+* `mgetl` speed have been improved for files with a lot of lines.
 
 Help pages:
 -----------
index e329296..d0c7f80 100644 (file)
@@ -3,24 +3,28 @@
 * Copyright (C) 2010 - DIGITEO - Allan CORNET
 * Copyright (C) 2010 - DIGITEO - Antoine ELIAS
 *
- * Copyright (C) 2012 - 2016 - Scilab Enterprises
- *
- * This file is hereby licensed under the terms of the GNU GPL v2.0,
- * pursuant to article 5.3.4 of the CeCILL v.2.1.
- * This file was originally licensed under the terms of the CeCILL v2.1,
- * and continues to be available under such terms.
- * For more information, see the COPYING file which you should have received
- * along with this program.
+* Copyright (C) 2012 - 2016 - Scilab Enterprises
+*
+* This file is hereby licensed under the terms of the GNU GPL v2.0,
+* pursuant to article 5.3.4 of the CeCILL v.2.1.
+* This file was originally licensed under the terms of the CeCILL v2.1,
+* and continues to be available under such terms.
+* For more information, see the COPYING file which you should have received
+* along with this program.
 *
 */
 /*--------------------------------------------------------------------------*/
 
+#include <iostream>
+#include <fstream>
+
 extern "C"
 {
 #include "mgetl.h"
 #include "freeArrayOfString.h"
 #include "charEncoding.h"
 #include "sci_malloc.h"
+#include "sciprint.h"
 }
 #include "filemanager.hxx"
 
@@ -36,7 +40,7 @@ extern "C"
 #endif
 #define BUFFER_SIZE 4096
 
-static const unsigned char UTF8_BOM[] = { 0xEF, 0xBB, 0xBF, 0x00 };
+static const unsigned char UTF8_BOM[] = {0xEF, 0xBB, 0xBF, 0x00};
 
 int mgetl(int iFileID, int iLineCount, wchar_t ***pwstLines)
 {
@@ -62,113 +66,163 @@ int mgetl(int iFileID, int iLineCount, wchar_t ***pwstLines)
     // check file is not empty
     if (ftell(fd) == 0)
     {
-        char cValues[4] = { 0x00, 0x00, 0x00, 0x00 };
+        char cValues[4] = {0x00, 0x00, 0x00, 0x00};
         if (fgets(cValues, 4 * sizeof(char), fd) != NULL)
         {
             // skip BOM
-            if (strcmp(cValues, (const char*) UTF8_BOM) != 0)
+            if (strcmp(cValues, (const char*)UTF8_BOM) != 0)
             {
                 rewind(fd);
             }
         }
     }
 
-    if (iLineCount > 0)
-    {
-        *pwstLines = (wchar_t**)MALLOC(iLineCount * sizeof(wchar_t*));
-        if (pwstLines == NULL)
-        {
-            return -1;
-        }
-    }
+    int orig = ftell(fd);
 
-    // allocate initial reading buffer, it will grow depending on line size
-    int iBufferSize = BUFFER_SIZE;
-    char *pstBuffer = (char *) malloc(iBufferSize * sizeof(char));
-    if (pstBuffer == NULL)
-    {
-        freeArrayOfWideString(*pwstLines, iLineCount);
-        *pwstLines = NULL;
-        return -1;
-    }
+#ifndef _MSC_VER
+    //must reopen the file
+    std::wstring wname = pFile->getFilename();
+    char* name = wide_string_to_UTF8(wname.data());
+    std::ifstream ifs(name);
+    FREE(name);
+#else
+    std::ifstream ifs(fd);
+#endif
+    //seek to same position
+    ifs.seekg(orig);
 
-    int iReadLineCount = 0;
-    while (fgets(pstBuffer, iBufferSize * sizeof(char), fd) != NULL)
-    {
-        iReadLineCount++;
-        *pwstLines = (wchar_t**)REALLOC(*pwstLines, iReadLineCount * sizeof(wchar_t*));
-        if (*pwstLines == NULL)
-        {
-            free(pstBuffer);
-            return -1;
-        }
+    std::list<std::string> lst;
+    std::string str;
 
-        // is the line complete in the buffer (= zero terminal found) ?
-        int len = strnlen(pstBuffer, iBufferSize);
-        int totalLen = len;
-        if (len >= iBufferSize - 1)
+    if (1)
+    {
+        bool lineReach = false;
+        std::string previous;
+        size_t offset = 0;
+        while (lst.size() < iLineCount && ifs.eof() == false)
         {
-            // no, there is another data to read for this line
-            // allocate a temporary buffer for reading it
-            char *pstBufferTmp = (char*) malloc(BUFFER_SIZE * sizeof(char));
-            if (pstBufferTmp == NULL)
-            {
-                freeArrayOfWideString(*pwstLines, iReadLineCount);
-                *pwstLines = NULL;
-                free(pstBuffer);
-                return -1;
-            }
-            char *pstNewBuffer = pstBuffer;
-            // loop until line is complete
-            do
+            int delimiter_size = 1;
+            size_t sp = previous.size();
+#define MAX_READ_LEN 262144
+            char buf[MAX_READ_LEN + 1] = {0};
+            ifs.read(buf, MAX_READ_LEN);
+            size_t s = strlen(buf);
+            //extract lines
+            char* ptr = buf;
+            for (int i = 0; i < s; ++i)
             {
-                // reallocate a new bigger buffer for the line
-                iBufferSize += BUFFER_SIZE;
-                pstNewBuffer = (char*) REALLOC(pstNewBuffer, iBufferSize * sizeof(char));
-                if (pstNewBuffer == NULL)
+                if (buf[i] == '\n')
                 {
-                    freeArrayOfWideString(*pwstLines, iReadLineCount);
-                    *pwstLines = NULL;
-                    free(pstBufferTmp);
-                    return -1;
+                    //delimit line
+                    buf[i] = '\0';
+                    if(buf[i - 1] == '\r')
+                    {
+                        buf[i - 1] = '\0';
+                        delimiter_size = 2;
+                    }
+
+                    //add line to list
+                    if (sp)
+                    {
+                        previous += ptr;
+                        lst.push_back(previous);
+#ifdef _MSC_VER
+                        offset += previous.size() + 2;
+#else
+                        offset += previous.size() + delimiter_size;
+#endif
+                        previous.clear();
+                    }
+                    else
+                    {
+                        lst.emplace_back(ptr);
+#ifdef _MSC_VER
+                        offset += strlen(ptr) + 2;
+#else
+                        offset += strlen(ptr) + delimiter_size;
+#endif
+                    }
+
+                    //move ptr to first next line char
+                    ptr = buf + i + 1;
+
+                    if (iLineCount != -1 && lst.size() >= iLineCount)
+                    {
+                        //rewind
+#ifndef _MSC_VER
+                        auto t = ifs.tellg();
+#else
+                        std::fpos_t t = ifs.tellg().seekpos();
+#endif
+                        if (t <= 0)
+                        {
+                            ifs.clear();
+                        }
+
+                        ifs.seekg(orig + offset, std::ios::beg);
+                        lineReach = true;
+                        break;
+                    }
                 }
+            }
 
-                // read the remaining data and copy it in the new buffer
-                if (fgets(pstBufferTmp, BUFFER_SIZE * sizeof(char), fd) != NULL)
-                {
-                    len = strnlen(pstBufferTmp, BUFFER_SIZE);
-                    totalLen += len;
-                    strncat(pstNewBuffer, pstBufferTmp, BUFFER_SIZE);
-                }
-                else
+            if (ptr == buf)
+            {
+                //long line
+                previous += buf;
+            }
+            else if (lineReach == false)
+            {
+                int offset = (int)(buf + s - ptr);
+                if (offset)
                 {
-                    break;
+                    if (!ifs.eof())
+                    {
+                        //some data stay in buf, rewind file to begin of this data and read it again
+                        ifs.seekg(-offset, std::ios::cur);
+                    }
+                    else
+                    {
+                        //some data stay in buf but oef is reached, add ptr data in list
+                        std::string str(ptr);
+                        lst.push_back(str);
+                    }
                 }
             }
-            while (len >= BUFFER_SIZE - 1);
-
-            free(pstBufferTmp);
-            // the bigger buffer becomes the current buffer
-            pstBuffer = pstNewBuffer;
         }
-        if ((totalLen > 0) && (pstBuffer[totalLen - 1] == '\n'))
+    }
+    else
+    {
+        while (lst.size() < iLineCount && std::getline(ifs, str))
         {
-            pstBuffer[totalLen - 1] = '\0';
-            if ((totalLen > 1) && (pstBuffer[totalLen - 2] == '\r'))
-            {
-                pstBuffer[totalLen - 2] = '\0';
-            }
+            lst.push_back(str);
         }
-        // add the line in the array
-        (*pwstLines)[iReadLineCount - 1] = to_wide_string(pstBuffer);
+    }
 
-        if ((iLineCount > 0) && (iReadLineCount >= iLineCount))
-        {
-            break;
-        }
+    int nbLinesOut = (int)lst.size();
+    if (nbLinesOut == 0)
+    {
+        return 0;
     }
 
-    free(pstBuffer);
-    return iReadLineCount;
-}
+    *pwstLines = (wchar_t**)MALLOC(nbLinesOut * sizeof(wchar_t**));
+    if (*pwstLines == NULL)
+    {
+        return -1;
+    }
 
+    for (int i = 0; i < nbLinesOut; ++i)
+    {
+        std::string s = lst.front();
+        (*pwstLines)[i] = to_wide_string(s.data());
+        lst.pop_front();
+    }
+
+#ifndef _MSC_VER
+    auto pos = ifs.tellg();
+    fseek(fd, pos, SEEK_SET);
+    ifs.close();
+#endif
+
+    return nbLinesOut;
+}
diff --git a/scilab/modules/fileio/tests/benchmarks/mgetl_verylongfile.tst b/scilab/modules/fileio/tests/benchmarks/mgetl_verylongfile.tst
new file mode 100644 (file)
index 0000000..3549ad0
--- /dev/null
@@ -0,0 +1,32 @@
+// ================================================================================
+// Scilab ( http://www.scilab.org/ ) - This file is part of Scilab
+// Copyright (C) 2016 - Scilab Enterprises
+//
+//  This file is distributed under the same license as the Scilab package.
+// ================================================================================
+
+//=================================================================================
+// Benchmark for mgetl function
+//==============================================================================
+
+// <-- BENCH NB RUN : 1 -->
+function a = randInt(start, stop, count)
+    a = int(rand(count, 1) * ((stop-1) - start) + start);
+endfunction
+
+function a = randStr(pool, count)
+    s = size(pool, "*");
+    i = rand(count, 1) * (s-1) + 1;
+    a = matrix(pool(i), -1, 1);
+endfunction
+
+filename = fullfile(TMPDIR, "verylongfile.txt");
+
+nb = 5000000;
+rI = randInt(1d5, 1d6, nb);
+strs = msprintf("%d,%d,%d,%d,%d,%d\n", rI, rI, rI, rI, rI, rI);
+mputl(strs, filename);
+
+// <-- BENCH START -->
+M = mgetl(filename);
+// <-- BENCH END -->