classification
Title: Nonblocking serial io using Arduino and Ubuntu 14.10 (Python 3.4.2) performance slowdown
Type: performance Stage:
Components: Interpreter Core, IO Versions: Python 3.4
process
Status: open Resolution:
Dependencies: Superseder:
Assigned To: Nosy List: MrYsLab, vstinner, willingc
Priority: normal Keywords:

Created on 2015-01-26 20:36 by MrYsLab, last changed 2015-01-27 15:06 by MrYsLab.

Files
File name Uploaded Description Edit
3 MrYsLab, 2015-01-27 14:50 pyprof2calltree file running "polling_digital_analog_io.py" on python 3.4.2
Messages (4)
msg234774 - (view) Author: Alan Yorinks (MrYsLab) Date: 2015-01-26 20:36
Folks,
   I am not trying to waste anyone's time.  If this is not the correct mailing list to get this problem resolved, please point me to the correct one.

   To summarize my problem, if I run the configuration below with python 3.4.2 on Linux, the program reacts to user input (changing a potentiometer) extremely slowly. If I run the exact same code on Windows, there is no slow down. Also, if I run the exact same setup using pypy 3.2.4, there is no slowdown.

   I also tried running the software on pypy 3.2.4 on Linux and there is no slowdown. The only problem I encounter is using Python 3.4.2 on Linux. Python 2.7.8 works without issue on both Linux and Windows.

   The setup to reproduce the problem is not complicated but requires PyMata to be installed on an Arduino Uno or Leonardo attached to the computer.

Prerequisites:
    OS: Ubuntu Linux 14.10.
   Python: 3.4.2
   PyMata 2.02 or can be installed from PyPi
   Requires PySerial 2.7
   Arduino Uno or Leonardo microcontroller with StandardFirmata installed.
  
   Program to be executed: located in the PyMata/examples/digital_analog_io directory. The file is polling_digital_analog_io.py.

How the problem exhibits itself:
   When adjusting the potentiometer to set the intensity level of the LED, the intensity level greatly lags the pot adjustment. On Python 2.7 on Linux and Windows, on Python 3.4.2 on Windows, and on pypy3.2.4 on Linux, there is no lag and the program behaves as expected.

   The only variable is the Linux version of Python 3.4.2, so it is my belief that python 3.4.2 for linux has issues. I will be happy to provide any additional information to help resolve this. Again if this is the wrong mailing list, please point me to the correct one.
msg234793 - (view) Author: Carol Willing (willingc) * (Python committer) Date: 2015-01-26 23:00
Alan, Thanks for reporting your issue. I have spent some time looking at it, and I have triaged it as best as I am able for the other developers and you. I am editing the issue title for clarity.

The issue is related to nonblocking i/o between a 'linux' (Ubuntu 14.10) system and an Arduino and the performance difference of the i/o channel noticed by the user on Python 3.4.2 and 2.7.9.

Third party packages (PyMata and pySerial) as well as threading module of CPython are used by the application that was reported in the original issue. Due to hardware constraints, I have not duplicated the issue.

I have done some research on the sources used by the application.

At the lowest level, i/o between the Arduino and the system is dependent on operating system implementation differences. There are differences between how i/o is implemented by pySerial for different operating systems. Comments in the pySerial source and docs state that nonblocking io and read differ and some functions (such as the nonblocking() method are highlighted as not portable.) There are also comments in the pySerial source code that not all systems support polling properly (presumably at their lower level os code).

Though perhaps not directly related (but at least tangentially),
this blog post explains some nonblocking differences between Python
3 and Python 2 (the last few paragraphs are most relevant).
http://ballingt.com/2014/03/01/nonblocking-stdin-in-python-3.html

---------------------------------------------------
Notes from a reverse walkthrough of the source code
---------------------------------------------------

In PyMata/examples/digital_analog_io/polling_digital_analog_io.py,
    analog = board.analog_read(POTENTIOMETER)

In PyMata/pymata.py, Python's threading module is imported, and
the PyMata class contains the API:
    data_lock = threading.Lock() is set
    analog_read(self, pin) method uses the data_lock and returns
        data value from pymata's command_handler's analog_response_table

Pymata's command_handler PyMata/pymata_command_handler.py has the
following information in its docstring:
    There is no blocking in either communications direction.

    There is blocking when accessing the data tables through the _data_lock

The get_analog_response_table method uses the data_lock.

PyMata's pymata_serial.py uses pySerial's class serial.Serial to
open and initialize a serial port. The following lines are in the
__init__ for PyMataSerial class:

        # without this, running python 3.4 is extremely sluggish
        if sys.platform == 'linux':
            # noinspection PyUnresolvedReferences
            self.arduino.nonblocking()

It should be noted that programs using pySerial's nonblocking()
method are not portable.

The following is the run method for PyMataSerial:

def run(self):
        """
        This method continually runs. If an incoming character is available on the serial port
        it is read and placed on the _command_deque
        @return: Never Returns
        """
        while not self.is_stopped():
            # we can get an OSError: [Errno9] Bad file descriptor when shutting down
            # just ignore it
            try:
                if self.arduino.inWaiting():
                    c = self.arduino.read()
                    self.command_deque.append(ord(c))
            except OSError:
                pass
            except IOError:
                self.stop()
        self.close()

The pySerial inWaiting() method is used to determine whether there
are bytes to be read by the read() method.
The source for pySerial http://svn.code.sf.net/p/pyserial/code/trunk/pyserial/serial/serialutil.py points out variations between Python 2
and 3 for string vs byte reading.

Interestingly, pySerial's Serial class can be assembled in two ways: 1) using pySerial's file-like emulation or 2) using io.RawIOBase.
From http://svn.code.sf.net/p/pyserial/code/trunk/pyserial/serial/serialposix.py,
# assemble Serial class with the platform specific implementation and the base
# for file-like behavior. for Python 2.6 and newer, that provide the new I/O
# library, derive from io.RawIOBase
try:
    import io
except ImportError:
    # classic version with our own file-like emulation
    class Serial(PosixSerial, FileLike):
        pass
else:
    # io library present
    class Serial(PosixSerial, io.RawIOBase):
        pass

class PosixPollSerial(Serial):
    """\
    Poll based read implementation. Not all systems support poll properly.
    However this one has better handling of errors, such as a device
    disconnecting while it's in use (e.g. USB-serial unplugged).
    """
msg234821 - (view) Author: Alan Yorinks (MrYsLab) Date: 2015-01-27 14:50
Additional information:
When using another example from PyMata, examples/digital_analog_io/callback_digital_analog_io.py, by adding the line:
        if sys.platform == 'linux':
            # noinspection PyUnresolvedReferences
            self.arduino.nonblocking()
Allowed the program to run without much noticeable lag. This program is a lot less io intensive.

Also, setting the pyserial init option, writeTimeout, did not seem to affect performance when running either test program.

I have run cProfile and ran the output through pyprof2calltree for the polling_digital_analog_io.py example.

I am attaching the result of pyprof2calltree to a file called 3. I will provide a similar file for the run with python 2.7.8 in an addtional comment (I can't attach more than 1 file per comment).
msg234822 - (view) Author: Alan Yorinks (MrYsLab) Date: 2015-01-27 15:06
I don't see the file I attached in the previous comment, so I have uploaded 4 files to google drive at:

https://drive.google.com/folderview?id=0B0adDMMjxksDRGtiWFowVUh0RlE&usp=sharing

These files are the result of running a cProfile for polling_digital_analog_io.py through pyprof2calltree. The file "2" is for python 2.7.8 and "3" is for python 3.4.2

They can be viewed using kcachegrind.

I have also included "2c" and "3c" for the results of running callback_digital_analog_io.py
History
Date User Action Args
2015-01-27 15:06:18MrYsLabsetmessages: + msg234822
2015-01-27 14:50:51MrYsLabsetfiles: + 3

messages: + msg234821
2015-01-26 23:01:39vstinnersetnosy: + vstinner
2015-01-26 23:00:04willingcsettitle: Python 3.4.2 running slow on Ubuntu 14.10 -> Nonblocking serial io using Arduino and Ubuntu 14.10 (Python 3.4.2) performance slowdown
nosy: + willingc

messages: + msg234793

components: + IO
type: performance
2015-01-26 20:36:56MrYsLabcreate