Hello - There are a large number of data points which need to be added to a CSV file. There is an indexing routine which cycles through all of the data points in the array and uses the CSV.AddString FB to add the strings to the buffer. When the buffer write is complete the items are saved to file using the CSV.WriteFile FB. Everything works as expected. The problem is that the process is extremely slow. The system takes 5 minutes to complete 5000 data points. The customer is requesting 45,000 data points.
I have taken the following steps to increased speed:
1. Change the write to a separate task with a cyclic time of 2ms
2. Changed the logic to only perform the WriteFile after adding 100 data points (cannot do all of the data points at once due to buffer size)
3. Tried to perform an CSV.AddString during every cycle without paying attention to the done from the FB. This did not report the data correctly.
Attached is the code I am using to process the data. Any advice on a different, faster approach would be much appreciated. Thank you for your time.
FUNCTION_BLOCKTestDataProcessorLREALVAR_INPUT
  bExecute: BOOL;
  InputArray         :   ARRAY[0..50000] OFLREAL;
  uiSampleNumber      :  UINT;END_VARVAR_IN_OUT
  cCSVWriter    : CSV.CSVWriter;END_VARVAR_OUTPUT
  bDone: BOOL;END_VARVAR
  i: UINT;
  sTempString       : STRING;
  csvAddTempString     : CSV.AddSTRING;
  csvWriteFile      : CSV.WriteFile;
  bcsvAddStringExecute  :  BOOL;
  bcsvWriteFileExecute  : BOOL;END_VARIFbExecuteANDNOTbDoneTHEN
  bcsvAddStringExecute :=TRUE;
 Â
  //ConverttheinputdatatostringtobestoredbytheCSVwriter
  sTempString :=LREAL_TO_STRING(InputArray[i]);
  //ChecktoseeifthepreviousstringAddiscomplete, andthereisnotafilewriterequestedandthenresettheaddstringbitandindexahead1
  IFcsvAddTempString.xDoneANDNOTbcsvWriteFileExecuteTHEN
    bcsvAddStringExecute :=FALSE;
    i:=i+1;
  END_IF
  //OnlycalltheWritefileevery100callsoftheaddstringFB, thiswasdonetoincreasespeed
  IF(iMOD100)=0ANDi>0ANDcsvAddTempString.xDoneTHEN
    bcsvWriteFileExecute :=TRUE;
  END_IF
 Â
//Whenthewritefileiscomplete, turnofftheExecutebit
  IFcsvWriteFile.xDoneTHEN
    bcsvWriteFileExecute :=FALSE;
  END_IF
 Â
//ThesecallsputthedatapointsintothebufferandthenwriteittothefileCSVAddTempSTRING(sInput:=sTempString,rCSVWriter:=cCSVWriter,xExecute:=bcsvAddStringExecute);csvWriteFile(xExecute:=csvAddTempString.xDone,rCSVWriter:=cCSVWriter);END_IF//ResettheindexandstopexecutionifIFNOTbExecuteTHEN
  bcsvAddStringExecute :=FALSE;
  i :=0;
  bDone :=FALSE;END_IF//SettheDONEbitwhentheendofarrayindexisreachedIFi>=uiSampleNumberANDbExecuteTHEN
  bDone :=TRUE;END_IF
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
You are inserting one point per task cycle, so if you have a 5 ms cycle time, it will take 250 seconds (4 minutes and 10 seconds) to insert 50k points.
How about a FOR loop calling CSVAddTempSTRING 100 times in a single cycle task? I would bet you will speed up operations by a similar factor (down to about 3 seconds).
Furthermore, IIRC VAR_INPUT mandates a copy of array is made upon each invocation. Try passing the array as VAR_IN_OUT CONSTANT to avoid copies.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Gronchi - Thank you for the response. Unfortunately it does not work to call the AddString FB (instance name is CSVAddTempString) repeatedly. I tried this method in the beginning. You have to wait for the Done bit of the FB before updating the input and calling it again. Otherwise it does not finish writing the data to the buffer and a lot of data goes missing.
You are correct that there is a better way to pass the data around: a pass by reference would probably be best. However, there is no memory shortage so I haven't tackled this yet.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Feels like it is still writing every cycle (beging that CSVAddTempSTRING is a synchronous fb).
That was another attempt to speed it up, although probably not to much effect. In the beginning I was trying to stay away from a state machine as that would inherently add one more scan to each change of state. However this became hard to manage, so i re-wrote the code in a CASE statement. It is much easier to follow now.... with one major change, I am concatenating a series of the data points into a single string and manually adding the commas. In this way, the WriteFile still has each data point separated by a comma but significantly reduces the number of AddString calls that need to be made. The limit on this is the CONCAT function can only take 255 characters at one time. It is much faster now, but still not fast enough. What I probably need to do is allocate an area of memory which is as large as the buffer for the CSV writer and fill that completely, then write to file. This should be fast enough.
IF NOTbExecute THEN
  step_number :=0;
  bDone :=FALSE;END_IF
  CASEstep_numberOF
    0:
      i:=0;
      IFbExecuteTHEN
        step_number :=10;
      END_IF
    10:
      i_step :=i+19;
      sTempString :='';
      FORi :=iTOi_stepDO
        sTempString :=Concat(STempString, Concat(LREAL_TO_STRING(InputArray[i]), ','));
      END_FOR
      sTempString :=Concat(STempString, LREAL_TO_STRING(InputArray[i]));
      step_number :=20;
    20:
      bcsvAddStringExecute :=TRUE;
      IFcsvAddTempString.xDoneTHEN
        bcsvAddStringExecute :=FALSE;
        step_number :=30;
      END_IF
    30:
      IF(iMOD100)=0THEN
        bcsvWriteFileExecute :=TRUE;
        step_number :=40;
      ELSE
        step_number :=10;
      END_IF
    40:
      IFcsvWriteFile.xDoneTHEN
        bcsvWriteFileExecute :=FALSE;
        IFi>=uiSampleNumberTHEN
          bDone :=TRUE;
          ELSE
          step_number :=10; Â
        END_IF
      END_IF
   Â
     Â
   Â
  END_CASE
 Â
 Â
 Â
 Â
//ThesecallsputthedatapointsintothebufferandthenwriteittothefileCSVAddTempSTRING(sInput:=sTempString,rCSVWriter:=cCSVWriter,xExecute:=bcsvAddStringExecute);csvWriteFile(xExecute:=bcsvWriteFileExecute,rCSVWriter:=cCSVWriter);
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Strings are no more than byte arrays ending with a 16#0 value. To reduce your treatment, just manage by yourself the length and offset for new string write inside a byte buffer. OSCAT lib may help for this.
Also you are still writing the csv format (with comas), you could aswell handle the cariage return and line feed.
Then, last but not least, you could try opening the file once, and then only writing the new data to it before closing it once done. This could be way more efficient thant opening and closing the file at each write.
If you would like to refer to this comment somewhere else in this project, copy and paste the following link:
Hello - There are a large number of data points which need to be added to a CSV file. There is an indexing routine which cycles through all of the data points in the array and uses the CSV.AddString FB to add the strings to the buffer. When the buffer write is complete the items are saved to file using the CSV.WriteFile FB. Everything works as expected. The problem is that the process is extremely slow. The system takes 5 minutes to complete 5000 data points. The customer is requesting 45,000 data points.
I have taken the following steps to increased speed:
1. Change the write to a separate task with a cyclic time of 2ms
2. Changed the logic to only perform the WriteFile after adding 100 data points (cannot do all of the data points at once due to buffer size)
3. Tried to perform an CSV.AddString during every cycle without paying attention to the done from the FB. This did not report the data correctly.
Attached is the code I am using to process the data. Any advice on a different, faster approach would be much appreciated. Thank you for your time.
You are inserting one point per task cycle, so if you have a 5 ms cycle time, it will take 250 seconds (4 minutes and 10 seconds) to insert 50k points.
How about a FOR loop calling CSVAddTempSTRING 100 times in a single cycle task? I would bet you will speed up operations by a similar factor (down to about 3 seconds).
Furthermore, IIRC VAR_INPUT mandates a copy of array is made upon each invocation. Try passing the array as VAR_IN_OUT CONSTANT to avoid copies.
Gronchi - Thank you for the response. Unfortunately it does not work to call the AddString FB (instance name is CSVAddTempString) repeatedly. I tried this method in the beginning. You have to wait for the Done bit of the FB before updating the input and calling it again. Otherwise it does not finish writing the data to the buffer and a lot of data goes missing.
You are correct that there is a better way to pass the data around: a pass by reference would probably be best. However, there is no memory shortage so I haven't tackled this yet.
Hi,
on which plc is this?
BR
Edwin
I don't get this :
Feels like it is still writing every cycle (beging that CSVAddTempSTRING is a synchronous fb).
Feels like it is still writing every cycle (beging that CSVAddTempSTRING is a synchronous fb).
That was another attempt to speed it up, although probably not to much effect. In the beginning I was trying to stay away from a state machine as that would inherently add one more scan to each change of state. However this became hard to manage, so i re-wrote the code in a CASE statement. It is much easier to follow now.... with one major change, I am concatenating a series of the data points into a single string and manually adding the commas. In this way, the WriteFile still has each data point separated by a comma but significantly reduces the number of AddString calls that need to be made. The limit on this is the CONCAT function can only take 255 characters at one time. It is much faster now, but still not fast enough. What I probably need to do is allocate an area of memory which is as large as the buffer for the CSV writer and fill that completely, then write to file. This should be fast enough.
This is on a Parker Automation Controller.
Strings are no more than byte arrays ending with a 16#0 value. To reduce your treatment, just manage by yourself the length and offset for new string write inside a byte buffer. OSCAT lib may help for this.
Also you are still writing the csv format (with comas), you could aswell handle the cariage return and line feed.
Then, last but not least, you could try opening the file once, and then only writing the new data to it before closing it once done. This could be way more efficient thant opening and closing the file at each write.