Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[GUI] Support gui.fps_limit and reduce idle power consumption #1611

Merged
merged 20 commits into from
Aug 6, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions python/taichi/misc/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,20 @@ def running(self, value):
elif not self.core.should_close:
self.core.should_close = 1

@property
def fps_limit(self):
if self.core.frame_delta_limit == 0:
return None
else:
return 1 / self.core.frame_delta_limit

@fps_limit.setter
def fps_limit(self, value):
if value is None:
self.core.frame_delta_limit = 0
else:
self.core.frame_delta_limit = 1 / value


def rgb_to_hex(c):
to255 = lambda x: np.clip(np.int32(x * 255), 0, 255)
Expand Down
10 changes: 5 additions & 5 deletions taichi/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -491,7 +491,7 @@ class GUI : public GUIBase {
std::string window_name;
int width, height;
int frame_id = 0;
const int fps = 60;
real frame_delta_limit = 1.0 / 60;
float64 start_time;
Array2D<Vector4> buffer;
std::vector<real> last_frame_interval;
Expand Down Expand Up @@ -806,12 +806,12 @@ class GUI : public GUIBase {
void update() {
frame_id++;
redraw_widgets();
while (taichi::Time::get_time() < last_frame_time + 1 / (real)fps)
;
taichi::Time::wait_until(last_frame_time + frame_delta_limit);
auto this_frame_time = taichi::Time::get_time();
if (last_frame_time != 0) {
last_frame_interval.push_back(taichi::Time::get_time() - last_frame_time);
last_frame_interval.push_back(this_frame_time - last_frame_time);
}
last_frame_time = taichi::Time::get_time();
last_frame_time = this_frame_time;
redraw();
// Some old examples / users don't even provide a `break` statement for us
// to terminate loop. So we have to terminate the program with RuntimeError
Expand Down
1 change: 1 addition & 0 deletions taichi/python/export_visual.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ void export_visual(py::module &m) {
.value("Release", Type::release);
py::class_<GUI>(m, "GUI")
.def(py::init<std::string, Vector2i>())
.def_readwrite("frame_delta_limit", &GUI::frame_delta_limit)
.def_readwrite("should_close", &GUI::should_close)
.def("get_canvas", &GUI::get_canvas, py::return_value_policy::reference)
.def("set_img",
Expand Down
18 changes: 18 additions & 0 deletions taichi/system/timer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,24 @@ void Time::sleep(double s) {
Time::usleep(s * 1e6_f64);
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here is a mush more accurate milliseconds level sleep implementation instead of Sleep(DWORD(us * 1e-3)) on windows .

#ifdef _WIN64

#include <Windows.h>
#pragma comment(lib, "Winmm.lib")

void win_sleep(DWORD ms) {
  if (ms == 0)
    Sleep(0);
  else {
    HANDLE hEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
    timeSetEvent(ms, 1, (LPTIMECALLBACK)hEvent , 0, TIME_ONESHOT | TIME_CALLBACK_EVENT_SET);
    WaitForSingleObject(hEvent, INFINITE);
    CloseHandle(hEvent);
  }
}
#endif

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds cool! But is that possible to sleep for microseconds (us) on Windows?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds cool! But is that possible to sleep for microseconds (us) on Windows?

It's sad truth that sleep accuracy can not be guaranteed when delay time is lower than 1ms :(
The only possible approach is to use high resolution clock to continually counting in a for loop , but it 's unable to release CPU time slice ...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sleeping for us seems tricky on win, how about just:

#ifndef _WIN64
} while (dt > 1e-4_f64);  // until dt <= 100us
#else
} while (dt > 1e-2_f64);  // until dt <= 10ms
#endif

Copy link
Contributor

@JYLeeLYJ JYLeeLYJ Jul 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1ms is more accurate ( 1/60 = 16.6 ms , 1/120 = 8.3ms , 1/40 = 25 ms )

#else
} while (dt > 1e-3_f64);  // until dt <= 1ms
#endif


void Time::wait_until(double t) {
double dt;
if (t < Time::get_time()) {
return;
}
do { // use system-provided sleep for large scale sleeping:
dt = (t - Time::get_time()) * 0.5;
if (dt <= 0) {
return;
}
Time::sleep(dt * 1e-3);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We set the precision as 100us. But relatively , dt * 1e-3 is too small .
As a result , Sleep(0) is always called on windows , which will not give up the CPU time slice usually.
I think Time::sleep(dt) or Time::sleep(dt * 0.5) is enough.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note that the unit of (t - Time::get_time()) is seconds, while the unit of Time::sleep is milliseconds.

Copy link
Contributor

@JYLeeLYJ JYLeeLYJ Jul 31, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Time::sleep should be seconds . See Time::usleep(s * 1e6_f64) .
Here are some actual value while running :
dt = 0.011... , dt = 0.008... and dt * 1e-3 is about 10 us

} while (dt > 1e-4_f64); // until dt <= 100us

// use an EBFE loop for small scale waiting:
while (Time::get_time() < t - 1e-6_f64)
; // until dt <= 1us
}

double Time::Timer::get_time() {
return Time::get_time();
}
Expand Down
2 changes: 1 addition & 1 deletion taichi/system/timer.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,8 @@ TI_NAMESPACE_BEGIN
class Time {
public:
static double get_time();

static uint64 get_cycles();
static void wait_until(double t);

static void usleep(double us);
static void sleep(double s);
Expand Down