[libcamera-devel] libcamera: V4L2Device: Remove the controls order assumption in updateControls()
diff mbox series

Message ID 20210415043625.3346627-1-hiroh@chromium.org
State Superseded
Headers show
Series
  • [libcamera-devel] libcamera: V4L2Device: Remove the controls order assumption in updateControls()
Related show

Commit Message

Hirokazu Honda April 15, 2021, 4:36 a.m. UTC
The original updateControls() has the assumption that ctrls and
v4l2Ctrls lists in the same order. It is dependent on the caller
implementation though. This changes updateControls()
implementation so that it works without the assumption.

Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
---
 src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
 1 file changed, 13 insertions(+), 13 deletions(-)

Comments

Laurent Pinchart April 20, 2021, 12:25 a.m. UTC | #1
Hi Hiro,

Thank you for the patch.

On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> The original updateControls() has the assumption that ctrls and
> v4l2Ctrls lists in the same order. It is dependent on the caller
> implementation though. This changes updateControls()
> implementation so that it works without the assumption.
> 
> Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> ---
>  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
>  1 file changed, 13 insertions(+), 13 deletions(-)
> 
> diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> index d4a9bb75..8fd79934 100644
> --- a/src/libcamera/v4l2_device.cpp
> +++ b/src/libcamera/v4l2_device.cpp
> @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
>  				const struct v4l2_ext_control *v4l2Ctrls,
>  				unsigned int count)
>  {
> -	unsigned int i = 0;
> -	for (auto &ctrl : *ctrls) {
> -		if (i == count)
> -			break;
> -
> -		const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> -		unsigned int id = ctrl.first;
> -		ControlValue &value = ctrl.second;
> +	for (unsigned int i = 0; i < count; i++) {
> +		const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> +		const unsigned int id = v4l2Ctrl.id;
> +		if (!ctrls->contains(id)) {
> +			LOG(V4L2, Error) << "ControlList doesn't contain id: "
> +					 << id;
> +			return;
> +		}
>  
> -		const auto iter = controls_.find(id);
> -		switch (iter->first->type()) {
> +		ControlValue value = ctrls->get(id);

This is less efficient, as the function now runs with O(n^2) complexity.
Given that updateControls() is private, why is requiring ctrls and
v4l2Ctrls to have the same order a problem ? The requirement should be
documented, but does it cause any issue ?

> +		switch (value.type()) {
>  		case ControlTypeInteger64:
> -			value.set<int64_t>(v4l2Ctrl->value64);
> +			value.set<int64_t>(v4l2Ctrl.value64);
>  			break;
>  
>  		case ControlTypeByte:
> @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
>  			 * \todo To be changed when support for string controls
>  			 * will be added.
>  			 */
> -			value.set<int32_t>(v4l2Ctrl->value);
> +			value.set<int32_t>(v4l2Ctrl.value);
>  			break;
>  		}
>  
> -		i++;
> +		ctrls->set(id, value);
>  	}
>  }
>
Hirokazu Honda April 21, 2021, 2:34 a.m. UTC | #2
Hi Laurent, Thanks for reviewing.


On Tue, Apr 20, 2021 at 9:25 AM Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Hiro,
>
> Thank you for the patch.
>
> On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> > The original updateControls() has the assumption that ctrls and
> > v4l2Ctrls lists in the same order. It is dependent on the caller
> > implementation though. This changes updateControls()
> > implementation so that it works without the assumption.
> >
> > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > ---
> >  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
> >  1 file changed, 13 insertions(+), 13 deletions(-)
> >
> > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > index d4a9bb75..8fd79934 100644
> > --- a/src/libcamera/v4l2_device.cpp
> > +++ b/src/libcamera/v4l2_device.cpp
> > @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
> >                               const struct v4l2_ext_control *v4l2Ctrls,
> >                               unsigned int count)
> >  {
> > -     unsigned int i = 0;
> > -     for (auto &ctrl : *ctrls) {
> > -             if (i == count)
> > -                     break;
> > -
> > -             const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> > -             unsigned int id = ctrl.first;
> > -             ControlValue &value = ctrl.second;
> > +     for (unsigned int i = 0; i < count; i++) {
> > +             const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> > +             const unsigned int id = v4l2Ctrl.id;
> > +             if (!ctrls->contains(id)) {
> > +                     LOG(V4L2, Error) << "ControlList doesn't contain id: "
> > +                                      << id;
> > +                     return;
> > +             }
> >
> > -             const auto iter = controls_.find(id);
> > -             switch (iter->first->type()) {
> > +             ControlValue value = ctrls->get(id);
>
> This is less efficient, as the function now runs with O(n^2) complexity.

Can you explain me why the time complexity is O(n^2)?
Since controls_ is based on std::unordered_map, find(), contains(),
get() and set() should be O(1).
So I think this is still O(n).

> Given that updateControls() is private, why is requiring ctrls and
> v4l2Ctrls to have the same order a problem ? The requirement should be
> documented, but does it cause any issue ?

I think the restriction is strange and unnecessary, and a caller might
have to pre-process to keep that.
Well, so far, the caller is only V4L2Device and we don't need anything
to keep the rule.
-Hiro

>
> > +             switch (value.type()) {
> >               case ControlTypeInteger64:
> > -                     value.set<int64_t>(v4l2Ctrl->value64);
> > +                     value.set<int64_t>(v4l2Ctrl.value64);
> >                       break;
> >
> >               case ControlTypeByte:
> > @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
> >                        * \todo To be changed when support for string controls
> >                        * will be added.
> >                        */
> > -                     value.set<int32_t>(v4l2Ctrl->value);
> > +                     value.set<int32_t>(v4l2Ctrl.value);
> >                       break;
> >               }
> >
> > -             i++;
> > +             ctrls->set(id, value);
> >       }
> >  }
> >
>
> --
> Regards,
>
> Laurent Pinchart
Laurent Pinchart April 21, 2021, 9:30 a.m. UTC | #3
Hi Hiro,

On Wed, Apr 21, 2021 at 11:34:58AM +0900, Hirokazu Honda wrote:
> On Tue, Apr 20, 2021 at 9:25 AM Laurent Pinchart wrote:
> > On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> > > The original updateControls() has the assumption that ctrls and
> > > v4l2Ctrls lists in the same order. It is dependent on the caller
> > > implementation though. This changes updateControls()
> > > implementation so that it works without the assumption.
> > >
> > > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > > ---
> > >  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
> > >  1 file changed, 13 insertions(+), 13 deletions(-)
> > >
> > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > index d4a9bb75..8fd79934 100644
> > > --- a/src/libcamera/v4l2_device.cpp
> > > +++ b/src/libcamera/v4l2_device.cpp
> > > @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > >                               const struct v4l2_ext_control *v4l2Ctrls,
> > >                               unsigned int count)
> > >  {
> > > -     unsigned int i = 0;
> > > -     for (auto &ctrl : *ctrls) {
> > > -             if (i == count)
> > > -                     break;
> > > -
> > > -             const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> > > -             unsigned int id = ctrl.first;
> > > -             ControlValue &value = ctrl.second;
> > > +     for (unsigned int i = 0; i < count; i++) {
> > > +             const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> > > +             const unsigned int id = v4l2Ctrl.id;
> > > +             if (!ctrls->contains(id)) {
> > > +                     LOG(V4L2, Error) << "ControlList doesn't contain id: "
> > > +                                      << id;
> > > +                     return;
> > > +             }
> > >
> > > -             const auto iter = controls_.find(id);
> > > -             switch (iter->first->type()) {
> > > +             ControlValue value = ctrls->get(id);
> >
> > This is less efficient, as the function now runs with O(n^2) complexity.
> 
> Can you explain me why the time complexity is O(n^2)?
> Since controls_ is based on std::unordered_map, find(), contains(),
> get() and set() should be O(1).
> So I think this is still O(n).

You're right that it's not O(n^2) as we use an unordered map.
std::unordered_map::find() is "constant on average, worst case linear in
the size of the container".

> > Given that updateControls() is private, why is requiring ctrls and
> > v4l2Ctrls to have the same order a problem ? The requirement should be
> > documented, but does it cause any issue ?
> 
> I think the restriction is strange and unnecessary, and a caller might
> have to pre-process to keep that.
> Well, so far, the caller is only V4L2Device and we don't need anything
> to keep the rule.

That was my point, it's a private function of the class :-) As all
callers currently guarantee the order, I'd defer this change until we
have a use case.

> > > +             switch (value.type()) {
> > >               case ControlTypeInteger64:
> > > -                     value.set<int64_t>(v4l2Ctrl->value64);
> > > +                     value.set<int64_t>(v4l2Ctrl.value64);
> > >                       break;
> > >
> > >               case ControlTypeByte:
> > > @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > >                        * \todo To be changed when support for string controls
> > >                        * will be added.
> > >                        */
> > > -                     value.set<int32_t>(v4l2Ctrl->value);
> > > +                     value.set<int32_t>(v4l2Ctrl.value);
> > >                       break;
> > >               }
> > >
> > > -             i++;
> > > +             ctrls->set(id, value);
> > >       }
> > >  }
> > >
Hirokazu Honda April 21, 2021, 9:37 a.m. UTC | #4
On Wed, Apr 21, 2021 at 6:30 PM Laurent Pinchart
<laurent.pinchart@ideasonboard.com> wrote:
>
> Hi Hiro,
>
> On Wed, Apr 21, 2021 at 11:34:58AM +0900, Hirokazu Honda wrote:
> > On Tue, Apr 20, 2021 at 9:25 AM Laurent Pinchart wrote:
> > > On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> > > > The original updateControls() has the assumption that ctrls and
> > > > v4l2Ctrls lists in the same order. It is dependent on the caller
> > > > implementation though. This changes updateControls()
> > > > implementation so that it works without the assumption.
> > > >
> > > > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > > > ---
> > > >  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
> > > >  1 file changed, 13 insertions(+), 13 deletions(-)
> > > >
> > > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > > index d4a9bb75..8fd79934 100644
> > > > --- a/src/libcamera/v4l2_device.cpp
> > > > +++ b/src/libcamera/v4l2_device.cpp
> > > > @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > >                               const struct v4l2_ext_control *v4l2Ctrls,
> > > >                               unsigned int count)
> > > >  {
> > > > -     unsigned int i = 0;
> > > > -     for (auto &ctrl : *ctrls) {
> > > > -             if (i == count)
> > > > -                     break;
> > > > -
> > > > -             const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> > > > -             unsigned int id = ctrl.first;
> > > > -             ControlValue &value = ctrl.second;
> > > > +     for (unsigned int i = 0; i < count; i++) {
> > > > +             const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> > > > +             const unsigned int id = v4l2Ctrl.id;
> > > > +             if (!ctrls->contains(id)) {
> > > > +                     LOG(V4L2, Error) << "ControlList doesn't contain id: "
> > > > +                                      << id;
> > > > +                     return;
> > > > +             }
> > > >
> > > > -             const auto iter = controls_.find(id);
> > > > -             switch (iter->first->type()) {
> > > > +             ControlValue value = ctrls->get(id);
> > >
> > > This is less efficient, as the function now runs with O(n^2) complexity.
> >
> > Can you explain me why the time complexity is O(n^2)?
> > Since controls_ is based on std::unordered_map, find(), contains(),
> > get() and set() should be O(1).
> > So I think this is still O(n).
>
> You're right that it's not O(n^2) as we use an unordered map.
> std::unordered_map::find() is "constant on average, worst case linear in
> the size of the container".
>
> > > Given that updateControls() is private, why is requiring ctrls and
> > > v4l2Ctrls to have the same order a problem ? The requirement should be
> > > documented, but does it cause any issue ?
> >
> > I think the restriction is strange and unnecessary, and a caller might
> > have to pre-process to keep that.
> > Well, so far, the caller is only V4L2Device and we don't need anything
> > to keep the rule.
>
> That was my point, it's a private function of the class :-) As all
> callers currently guarantee the order, I'd defer this change until we
> have a use case.
>

I forgot mentioning there is no guaranteed upon traversing std::unordered_map.
https://stackoverflow.com/questions/50870951/iterating-over-unordered-map-c

So I think as long as std::unordered_map is used, the original
implementation is problematic?
-Hiro

> > > > +             switch (value.type()) {
> > > >               case ControlTypeInteger64:
> > > > -                     value.set<int64_t>(v4l2Ctrl->value64);
> > > > +                     value.set<int64_t>(v4l2Ctrl.value64);
> > > >                       break;
> > > >
> > > >               case ControlTypeByte:
> > > > @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > >                        * \todo To be changed when support for string controls
> > > >                        * will be added.
> > > >                        */
> > > > -                     value.set<int32_t>(v4l2Ctrl->value);
> > > > +                     value.set<int32_t>(v4l2Ctrl.value);
> > > >                       break;
> > > >               }
> > > >
> > > > -             i++;
> > > > +             ctrls->set(id, value);
> > > >       }
> > > >  }
> > > >
>
> --
> Regards,
>
> Laurent Pinchart
Jacopo Mondi April 21, 2021, 12:37 p.m. UTC | #5
Hi Hiro,

On Wed, Apr 21, 2021 at 06:37:45PM +0900, Hirokazu Honda wrote:
> On Wed, Apr 21, 2021 at 6:30 PM Laurent Pinchart
> <laurent.pinchart@ideasonboard.com> wrote:
> >
> > Hi Hiro,
> >
> > On Wed, Apr 21, 2021 at 11:34:58AM +0900, Hirokazu Honda wrote:
> > > On Tue, Apr 20, 2021 at 9:25 AM Laurent Pinchart wrote:
> > > > On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> > > > > The original updateControls() has the assumption that ctrls and
> > > > > v4l2Ctrls lists in the same order. It is dependent on the caller
> > > > > implementation though. This changes updateControls()
> > > > > implementation so that it works without the assumption.
> > > > >
> > > > > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > > > > ---
> > > > >  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
> > > > >  1 file changed, 13 insertions(+), 13 deletions(-)
> > > > >
> > > > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > > > index d4a9bb75..8fd79934 100644
> > > > > --- a/src/libcamera/v4l2_device.cpp
> > > > > +++ b/src/libcamera/v4l2_device.cpp
> > > > > @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > > >                               const struct v4l2_ext_control *v4l2Ctrls,
> > > > >                               unsigned int count)
> > > > >  {
> > > > > -     unsigned int i = 0;
> > > > > -     for (auto &ctrl : *ctrls) {
> > > > > -             if (i == count)
> > > > > -                     break;
> > > > > -
> > > > > -             const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> > > > > -             unsigned int id = ctrl.first;
> > > > > -             ControlValue &value = ctrl.second;
> > > > > +     for (unsigned int i = 0; i < count; i++) {
> > > > > +             const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> > > > > +             const unsigned int id = v4l2Ctrl.id;
> > > > > +             if (!ctrls->contains(id)) {
> > > > > +                     LOG(V4L2, Error) << "ControlList doesn't contain id: "
> > > > > +                                      << id;
> > > > > +                     return;
> > > > > +             }
> > > > >
> > > > > -             const auto iter = controls_.find(id);
> > > > > -             switch (iter->first->type()) {
> > > > > +             ControlValue value = ctrls->get(id);
> > > >
> > > > This is less efficient, as the function now runs with O(n^2) complexity.
> > >
> > > Can you explain me why the time complexity is O(n^2)?
> > > Since controls_ is based on std::unordered_map, find(), contains(),
> > > get() and set() should be O(1).
> > > So I think this is still O(n).

I read that all these functions are "in worst case" linear to the
length of the input in complexity

> >
> > You're right that it's not O(n^2) as we use an unordered map.
> > std::unordered_map::find() is "constant on average, worst case linear in
> > the size of the container".
> >
> > > > Given that updateControls() is private, why is requiring ctrls and
> > > > v4l2Ctrls to have the same order a problem ? The requirement should be
> > > > documented, but does it cause any issue ?
> > >
> > > I think the restriction is strange and unnecessary, and a caller might
> > > have to pre-process to keep that.
> > > Well, so far, the caller is only V4L2Device and we don't need anything
> > > to keep the rule.
> >
> > That was my point, it's a private function of the class :-) As all
> > callers currently guarantee the order, I'd defer this change until we
> > have a use case.
> >
>
> I forgot mentioning there is no guaranteed upon traversing std::unordered_map.
> https://stackoverflow.com/questions/50870951/iterating-over-unordered-map-c
>
> So I think as long as std::unordered_map is used, the original
> implementation is problematic?

If you look at the callers, the v4l2Ctrls are filled in by iterating
on the ControlList (which is an unordered map).

Regardless of how the ControlList is sorted, v4l2Ctrls will have the
same sorting order. There's a comment that explains that in
getControls():

	/*
	 * Start by filling the ControlList. This can't be combined with filling
	 * v4l2Ctrls, as updateControls() relies on both containers having the
	 * same order, and the control list is based on a map, which is not
	 * sorted by insertion order.
	 */

I might have missed what issue you have encountered.

I'm anyway not opposed to this change, as the ordering assumption is a
bit weird, even if it should be safe.

Changing such a core feature would require some careful testing, and
if there's no actual reason to change it I would be cautious to do so
but I'm not opposed...


> -Hiro
>
> > > > > +             switch (value.type()) {
> > > > >               case ControlTypeInteger64:
> > > > > -                     value.set<int64_t>(v4l2Ctrl->value64);
> > > > > +                     value.set<int64_t>(v4l2Ctrl.value64);
> > > > >                       break;
> > > > >
> > > > >               case ControlTypeByte:
> > > > > @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > > >                        * \todo To be changed when support for string controls
> > > > >                        * will be added.
> > > > >                        */
> > > > > -                     value.set<int32_t>(v4l2Ctrl->value);
> > > > > +                     value.set<int32_t>(v4l2Ctrl.value);
> > > > >                       break;
> > > > >               }
> > > > >
> > > > > -             i++;
> > > > > +             ctrls->set(id, value);
> > > > >       }
> > > > >  }
> > > > >
> >
> > --
> > Regards,
> >
> > Laurent Pinchart
> _______________________________________________
> libcamera-devel mailing list
> libcamera-devel@lists.libcamera.org
> https://lists.libcamera.org/listinfo/libcamera-devel
Hirokazu Honda April 22, 2021, 2:14 a.m. UTC | #6
On Wed, Apr 21, 2021 at 9:36 PM Jacopo Mondi <jacopo@jmondi.org> wrote:
>
> Hi Hiro,
>
> On Wed, Apr 21, 2021 at 06:37:45PM +0900, Hirokazu Honda wrote:
> > On Wed, Apr 21, 2021 at 6:30 PM Laurent Pinchart
> > <laurent.pinchart@ideasonboard.com> wrote:
> > >
> > > Hi Hiro,
> > >
> > > On Wed, Apr 21, 2021 at 11:34:58AM +0900, Hirokazu Honda wrote:
> > > > On Tue, Apr 20, 2021 at 9:25 AM Laurent Pinchart wrote:
> > > > > On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> > > > > > The original updateControls() has the assumption that ctrls and
> > > > > > v4l2Ctrls lists in the same order. It is dependent on the caller
> > > > > > implementation though. This changes updateControls()
> > > > > > implementation so that it works without the assumption.
> > > > > >
> > > > > > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > > > > > ---
> > > > > >  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
> > > > > >  1 file changed, 13 insertions(+), 13 deletions(-)
> > > > > >
> > > > > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > > > > index d4a9bb75..8fd79934 100644
> > > > > > --- a/src/libcamera/v4l2_device.cpp
> > > > > > +++ b/src/libcamera/v4l2_device.cpp
> > > > > > @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > > > >                               const struct v4l2_ext_control *v4l2Ctrls,
> > > > > >                               unsigned int count)
> > > > > >  {
> > > > > > -     unsigned int i = 0;
> > > > > > -     for (auto &ctrl : *ctrls) {
> > > > > > -             if (i == count)
> > > > > > -                     break;
> > > > > > -
> > > > > > -             const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> > > > > > -             unsigned int id = ctrl.first;
> > > > > > -             ControlValue &value = ctrl.second;
> > > > > > +     for (unsigned int i = 0; i < count; i++) {
> > > > > > +             const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> > > > > > +             const unsigned int id = v4l2Ctrl.id;
> > > > > > +             if (!ctrls->contains(id)) {
> > > > > > +                     LOG(V4L2, Error) << "ControlList doesn't contain id: "
> > > > > > +                                      << id;
> > > > > > +                     return;
> > > > > > +             }
> > > > > >
> > > > > > -             const auto iter = controls_.find(id);
> > > > > > -             switch (iter->first->type()) {
> > > > > > +             ControlValue value = ctrls->get(id);
> > > > >
> > > > > This is less efficient, as the function now runs with O(n^2) complexity.
> > > >
> > > > Can you explain me why the time complexity is O(n^2)?
> > > > Since controls_ is based on std::unordered_map, find(), contains(),
> > > > get() and set() should be O(1).
> > > > So I think this is still O(n).
>
> I read that all these functions are "in worst case" linear to the
> length of the input in complexity
>
> > >
> > > You're right that it's not O(n^2) as we use an unordered map.
> > > std::unordered_map::find() is "constant on average, worst case linear in
> > > the size of the container".
> > >
> > > > > Given that updateControls() is private, why is requiring ctrls and
> > > > > v4l2Ctrls to have the same order a problem ? The requirement should be
> > > > > documented, but does it cause any issue ?
> > > >
> > > > I think the restriction is strange and unnecessary, and a caller might
> > > > have to pre-process to keep that.
> > > > Well, so far, the caller is only V4L2Device and we don't need anything
> > > > to keep the rule.
> > >
> > > That was my point, it's a private function of the class :-) As all
> > > callers currently guarantee the order, I'd defer this change until we
> > > have a use case.
> > >
> >
> > I forgot mentioning there is no guaranteed upon traversing std::unordered_map.
> > https://stackoverflow.com/questions/50870951/iterating-over-unordered-map-c
> >
> > So I think as long as std::unordered_map is used, the original
> > implementation is problematic?
>
> If you look at the callers, the v4l2Ctrls are filled in by iterating
> on the ControlList (which is an unordered map).
>
> Regardless of how the ControlList is sorted, v4l2Ctrls will have the
> same sorting order. There's a comment that explains that in
> getControls():
>
>         /*
>          * Start by filling the ControlList. This can't be combined with filling
>          * v4l2Ctrls, as updateControls() relies on both containers having the
>          * same order, and the control list is based on a map, which is not
>          * sorted by insertion order.
>          */
>

Thanks. I missed this comment.

> I might have missed what issue you have encountered.
>
> I'm anyway not opposed to this change, as the ordering assumption is a
> bit weird, even if it should be safe.
>
> Changing such a core feature would require some careful testing, and
> if there's no actual reason to change it I would be cautious to do so
> but I'm not opposed...
>

I actually have never faced any issue due to this.
As a reader, this assumption is unnecessary and thus feels strange for me.
Removing the assumption is simple enough I believe, so I hope this
change makes sense to everyone.
-Hiro

>
> > -Hiro
> >
> > > > > > +             switch (value.type()) {
> > > > > >               case ControlTypeInteger64:
> > > > > > -                     value.set<int64_t>(v4l2Ctrl->value64);
> > > > > > +                     value.set<int64_t>(v4l2Ctrl.value64);
> > > > > >                       break;
> > > > > >
> > > > > >               case ControlTypeByte:
> > > > > > @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > > > >                        * \todo To be changed when support for string controls
> > > > > >                        * will be added.
> > > > > >                        */
> > > > > > -                     value.set<int32_t>(v4l2Ctrl->value);
> > > > > > +                     value.set<int32_t>(v4l2Ctrl.value);
> > > > > >                       break;
> > > > > >               }
> > > > > >
> > > > > > -             i++;
> > > > > > +             ctrls->set(id, value);
> > > > > >       }
> > > > > >  }
> > > > > >
> > >
> > > --
> > > Regards,
> > >
> > > Laurent Pinchart
> > _______________________________________________
> > libcamera-devel mailing list
> > libcamera-devel@lists.libcamera.org
> > https://lists.libcamera.org/listinfo/libcamera-devel
Jacopo Mondi April 22, 2021, 6:16 a.m. UTC | #7
Hi Hiro,

One more comment on the patch itself


On Thu, Apr 22, 2021 at 11:14:59AM +0900, Hirokazu Honda wrote:
> On Wed, Apr 21, 2021 at 9:36 PM Jacopo Mondi <jacopo@jmondi.org> wrote:
> >
> > Hi Hiro,
> >
> > On Wed, Apr 21, 2021 at 06:37:45PM +0900, Hirokazu Honda wrote:
> > > On Wed, Apr 21, 2021 at 6:30 PM Laurent Pinchart
> > > <laurent.pinchart@ideasonboard.com> wrote:
> > > >
> > > > Hi Hiro,
> > > >
> > > > On Wed, Apr 21, 2021 at 11:34:58AM +0900, Hirokazu Honda wrote:
> > > > > On Tue, Apr 20, 2021 at 9:25 AM Laurent Pinchart wrote:
> > > > > > On Thu, Apr 15, 2021 at 01:36:25PM +0900, Hirokazu Honda wrote:
> > > > > > > The original updateControls() has the assumption that ctrls and
> > > > > > > v4l2Ctrls lists in the same order. It is dependent on the caller
> > > > > > > implementation though. This changes updateControls()
> > > > > > > implementation so that it works without the assumption.
> > > > > > >
> > > > > > > Signed-off-by: Hirokazu Honda <hiroh@chromium.org>
> > > > > > > ---
> > > > > > >  src/libcamera/v4l2_device.cpp | 26 +++++++++++++-------------
> > > > > > >  1 file changed, 13 insertions(+), 13 deletions(-)
> > > > > > >
> > > > > > > diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
> > > > > > > index d4a9bb75..8fd79934 100644
> > > > > > > --- a/src/libcamera/v4l2_device.cpp
> > > > > > > +++ b/src/libcamera/v4l2_device.cpp
> > > > > > > @@ -525,19 +525,19 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > > > > >                               const struct v4l2_ext_control *v4l2Ctrls,
> > > > > > >                               unsigned int count)
> > > > > > >  {
> > > > > > > -     unsigned int i = 0;
> > > > > > > -     for (auto &ctrl : *ctrls) {
> > > > > > > -             if (i == count)
> > > > > > > -                     break;
> > > > > > > -
> > > > > > > -             const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
> > > > > > > -             unsigned int id = ctrl.first;
> > > > > > > -             ControlValue &value = ctrl.second;
> > > > > > > +     for (unsigned int i = 0; i < count; i++) {
> > > > > > > +             const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
> > > > > > > +             const unsigned int id = v4l2Ctrl.id;
> > > > > > > +             if (!ctrls->contains(id)) {
> > > > > > > +                     LOG(V4L2, Error) << "ControlList doesn't contain id: "
> > > > > > > +                                      << id;
> > > > > > > +                     return;
> > > > > > > +             }

This can't happen, as this is a private function and we control the
callers

> > > > > > >
> > > > > > > -             const auto iter = controls_.find(id);
> > > > > > > -             switch (iter->first->type()) {
> > > > > > > +             ControlValue value = ctrls->get(id);
> > > > > >
> > > > > > This is less efficient, as the function now runs with O(n^2) complexity.
> > > > >
> > > > > Can you explain me why the time complexity is O(n^2)?
> > > > > Since controls_ is based on std::unordered_map, find(), contains(),
> > > > > get() and set() should be O(1).
> > > > > So I think this is still O(n).
> >
> > I read that all these functions are "in worst case" linear to the
> > length of the input in complexity
> >
> > > >
> > > > You're right that it's not O(n^2) as we use an unordered map.
> > > > std::unordered_map::find() is "constant on average, worst case linear in
> > > > the size of the container".
> > > >
> > > > > > Given that updateControls() is private, why is requiring ctrls and
> > > > > > v4l2Ctrls to have the same order a problem ? The requirement should be
> > > > > > documented, but does it cause any issue ?
> > > > >
> > > > > I think the restriction is strange and unnecessary, and a caller might
> > > > > have to pre-process to keep that.
> > > > > Well, so far, the caller is only V4L2Device and we don't need anything
> > > > > to keep the rule.
> > > >
> > > > That was my point, it's a private function of the class :-) As all
> > > > callers currently guarantee the order, I'd defer this change until we
> > > > have a use case.
> > > >
> > >
> > > I forgot mentioning there is no guaranteed upon traversing std::unordered_map.
> > > https://stackoverflow.com/questions/50870951/iterating-over-unordered-map-c
> > >
> > > So I think as long as std::unordered_map is used, the original
> > > implementation is problematic?
> >
> > If you look at the callers, the v4l2Ctrls are filled in by iterating
> > on the ControlList (which is an unordered map).
> >
> > Regardless of how the ControlList is sorted, v4l2Ctrls will have the
> > same sorting order. There's a comment that explains that in
> > getControls():
> >
> >         /*
> >          * Start by filling the ControlList. This can't be combined with filling
> >          * v4l2Ctrls, as updateControls() relies on both containers having the
> >          * same order, and the control list is based on a map, which is not
> >          * sorted by insertion order.
> >          */
> >
>
> Thanks. I missed this comment.
>
> > I might have missed what issue you have encountered.
> >
> > I'm anyway not opposed to this change, as the ordering assumption is a
> > bit weird, even if it should be safe.
> >
> > Changing such a core feature would require some careful testing, and
> > if there's no actual reason to change it I would be cautious to do so
> > but I'm not opposed...
> >
>
> I actually have never faced any issue due to this.
> As a reader, this assumption is unnecessary and thus feels strange for me.
> Removing the assumption is simple enough I believe, so I hope this
> change makes sense to everyone.
> -Hiro
>

Makes sense, the caller should be updated too to remove the
assumption too then, ideally removing the double for loop in
getControls().

The test covers updateControls() enough that making sure they pass
it's ok to consier this change safe.

Thanks
   j


> >
> > > -Hiro
> > >
> > > > > > > +             switch (value.type()) {
> > > > > > >               case ControlTypeInteger64:
> > > > > > > -                     value.set<int64_t>(v4l2Ctrl->value64);
> > > > > > > +                     value.set<int64_t>(v4l2Ctrl.value64);
> > > > > > >                       break;
> > > > > > >
> > > > > > >               case ControlTypeByte:
> > > > > > > @@ -552,11 +552,11 @@ void V4L2Device::updateControls(ControlList *ctrls,
> > > > > > >                        * \todo To be changed when support for string controls
> > > > > > >                        * will be added.
> > > > > > >                        */
> > > > > > > -                     value.set<int32_t>(v4l2Ctrl->value);
> > > > > > > +                     value.set<int32_t>(v4l2Ctrl.value);
> > > > > > >                       break;
> > > > > > >               }
> > > > > > >
> > > > > > > -             i++;
> > > > > > > +             ctrls->set(id, value);
> > > > > > >       }
> > > > > > >  }
> > > > > > >
> > > >
> > > > --
> > > > Regards,
> > > >
> > > > Laurent Pinchart
> > > _______________________________________________
> > > libcamera-devel mailing list
> > > libcamera-devel@lists.libcamera.org
> > > https://lists.libcamera.org/listinfo/libcamera-devel

Patch
diff mbox series

diff --git a/src/libcamera/v4l2_device.cpp b/src/libcamera/v4l2_device.cpp
index d4a9bb75..8fd79934 100644
--- a/src/libcamera/v4l2_device.cpp
+++ b/src/libcamera/v4l2_device.cpp
@@ -525,19 +525,19 @@  void V4L2Device::updateControls(ControlList *ctrls,
 				const struct v4l2_ext_control *v4l2Ctrls,
 				unsigned int count)
 {
-	unsigned int i = 0;
-	for (auto &ctrl : *ctrls) {
-		if (i == count)
-			break;
-
-		const struct v4l2_ext_control *v4l2Ctrl = &v4l2Ctrls[i];
-		unsigned int id = ctrl.first;
-		ControlValue &value = ctrl.second;
+	for (unsigned int i = 0; i < count; i++) {
+		const struct v4l2_ext_control &v4l2Ctrl = v4l2Ctrls[i];
+		const unsigned int id = v4l2Ctrl.id;
+		if (!ctrls->contains(id)) {
+			LOG(V4L2, Error) << "ControlList doesn't contain id: "
+					 << id;
+			return;
+		}
 
-		const auto iter = controls_.find(id);
-		switch (iter->first->type()) {
+		ControlValue value = ctrls->get(id);
+		switch (value.type()) {
 		case ControlTypeInteger64:
-			value.set<int64_t>(v4l2Ctrl->value64);
+			value.set<int64_t>(v4l2Ctrl.value64);
 			break;
 
 		case ControlTypeByte:
@@ -552,11 +552,11 @@  void V4L2Device::updateControls(ControlList *ctrls,
 			 * \todo To be changed when support for string controls
 			 * will be added.
 			 */
-			value.set<int32_t>(v4l2Ctrl->value);
+			value.set<int32_t>(v4l2Ctrl.value);
 			break;
 		}
 
-		i++;
+		ctrls->set(id, value);
 	}
 }