mirror of
https://github.com/systemed/tilemaker
synced 2025-02-22 06:24:08 +01:00

* extract ClipCache to own file
Some housekeeping: extract clip_cache.cpp
* templatize ClipCache, apply to MultiLineStrings
This provides a very small benefit. I think the reason is two-fold:
there aren't many multilinestrings (relative to multipolygons), and
clipping them is less expensive.
Still, it did seem to provide a small boost, so leaving it in.
* housekeeping: move test, minunit
* --log-tile-timings: verbose timing logs
This isn't super useful to end users, but is useful for developers.
If it's not OK to leave it in, let me know & I'll revert it.
You can then process the log:
```bash
$ for x in {0..14}; do echo -n "z$x "; cat log-write-node-attributes.txt | grep ' took ' | sort -nk3 | grep z$x/ | awk 'BEGIN { min = 999999; max = 0; }; { n += 1; t += $3; if ($3 > max) { max = $3; max_id = $1; } } END { print n, t, t/n, max " (" max_id ")" }'; done
z0 1 7.04769 7.04769 7.047685 (z0/0/0)
z1 1 9.76067 9.76067 9.760671 (z1/0/0)
z2 1 9.98514 9.98514 9.985141 (z2/1/1)
z3 1 9.98514 9.98514 9.985141 (z3/2/2)
z4 2 14.4699 7.23493 8.610035 (z4/5/5)
z5 2 20.828 10.414 13.956526 (z5/10/11)
z6 5 6464.05 1292.81 3206.252711 (z6/20/23)
z7 13 11306.4 869.727 3275.475707 (z7/40/46)
z8 35 15787.1 451.061 2857.506681 (z8/81/92)
z9 86 20723.8 240.974 1605.788985 (z9/162/186)
z10 277 25456.8 91.9018 778.311785 (z10/331/369)
z11 960 28851.3 30.0534 627.351078 (z11/657/735)
z12 3477 24031.6 6.91158 451.122972 (z12/1315/1471)
z13 13005 13763.7 1.05834 156.074701 (z13/2631/2943)
z14 50512 24214.7 0.479385 106.358450 (z14/5297/5916)
```
This shows each zoom's # of tiles, total time, average time, worst case
time (and the tile that caused it).
In general, lower zooms are slower than higher zooms. This seems
intuitively reasonable, as the lower zoom often contains all of
the objects in the higher zoom.
I would have guessed that a lower zoom would cost 4x the next higher
zoom on a per-tile basis. That's sort of the case for `z12->z13`,
`z11->z12`, `z10->z11`, and `z9->z10`. But not so for other zooms,
where it's more like a 2x cost.
Looking at `z5->z6`, we see a big jump from 10ms/tile to 1,292ms/tile.
This is probably because `water` has a minzoom of 6.
This all makes me think that the next big gain will be from re-using
simplifications.
This is sort of the mirror image of the clip cache:
- the clip cache avoids redundant clipping, and needs to be computed
from lower zooms to higher zooms
- a simplification cache could make simplifying cheaper, but needs to
be computed from higher zooms to lower zooms
The simplification cache also has two other wrinkles:
1. Is it even valid? e.g. is `simplify(object, 4)` the same as
`simplify(simplify(object, 2), 2)` ? Maybe it doesn't have to be the
same, because users are already accepting that we're losing accuracy
when we simplify.
2. Rendering an object at `z - 1`, needds to (potentially) stitch together
that object from 4 tiles at `z`. If those have each been simplified,
we may introduce odd seams where the terminal points don't line up.
* more, smaller caches; run destructors outside lock
* use explicit types
* don't populate unnecessary vectors
* reserve vectors appropriately
* don't eagerly call way:IsClosed()
This saves a very little bit of time, but more importantly, tees up
lazily evaluating the nodes in a way.
* remove locks from geometry stores
Rather than locking on each store call, threads lease a range of the
ID space for points/lines/multilines/polygons. When the thread ends,
it return the lease.
This has some implications:
- the IDs are no longer guaranteed to be contiguous
- shapefiles are a bit weird, as they're loaded on the main
thread -- so their lease won't be returned until program
end. This is fine, just pointing it out.
This didn't actually seem to affect runtime that much on my 16 core
machine, but I think it'd help on machines with more cores.
* increase attributestore shards
When scaling to 32+ cores, this shows up as an issue. Try a really
blunt hammer fix.
* read_pbf: less lock contention on status
`std::cout` has some internal locks -- instead, let's synchronize
explicitly outside of it so we control the contention.
If a worker fails to get the lock, just skip that worker's update.
* tile_worker: do syscall 1x/thread, not 1x/tile
* tilemaker: avoid lock contention on status update
If a worker can't get the lock, just skip their update.
* Revert "don't eagerly call way:IsClosed()"
This reverts commit 3e7b9b62d1
.
This commit came about from some experiments that I had done
pre-SortedNodeStore.
In that world, lazily evaluating the nodes of a way provided a
meaningful savings if the way was ultimately discarded by the Lua
code.
Post-SortedNodeStore, it doesn't seem to matter as much. Which is great,
as it means the store is much faster, but also means this commit is
just noise.
You can see the POC code in https://github.com/cldellow/tilemaker/tree/lazy-way-nodes
* update ifdef guard, add comments
* lazy way geometries
Tilemaker previously stored the 2D geometries it produced from ways.
This commit makes Tilemaker use the OSM way store to generate linestrings
and polygons that originated with an OSM way. You can get the old
behaviour with `--materialize-geometries`, which is a sensible choice if
you are not memory constrained.
For GB:
before (available via `--materialize-geometries`): 2m11s, 9600MB
this commit: 2m20s, 6400MB
So ~8% slower, but 33% less memory.
I think it's probably reasonable for this to be the default, which has
nice symmetry with compressed nodes and compressed ways being the
default.
Building NA with --store still seems OK - 36min. I was concerned that
the increased node store lookups could be vulnerable to thrashing.
I do see some stuttering during tile writing, but I think the decreased
read iops from a smaller geometry store balance out the increased
read iops from looking up nodes. A future investigation might be to
have SortedWayStore store latplons rather than node IDs -- a bit
more memory, but should be less CPU and less vulnerable to thrashing.
* improve tile coordinate generation
Before writing, we compute the set of tiles to be written.
There were two opportunities for improvement here:
- determining which tiles were covered by our objects: we previously
used a `std::set`, which has poor memory and runtime behaviour.
Instead, use a fixed size `std::vector<bool>` -- this takes 64MB
at z14, but gives much faster insert/query times
- determining if tiles were covered by clipping box: we used
boost's intersect algorithm before, which required constructing
a TileBbox and was a bit slow. In the case where the tile is
contained in a z6 tile that is wholly covered by the clipping
box, we can short-circuit
This has the most impact when the set of objects or tiles is very
large--e.g. Antarctica, North America or bigger.
* SortedNodeStore: only do arena allocations
On a 48-core server, I noticed lock contention on the mmap allocator.
So let's just always use pools of memory, and pick a bigger pool size.
This means we'll sometimes allocate memory that we don't use.
In the extreme case of Monaco, we only need like 200KB, but we'll
allocate several megs.
As you scale to larger PBFs, the waste trends to 0%, so this should
be fine in practice.
* remove TODO
* fix Windows build
D'oh, clock_gettime is Linux-ish. `std::chrono` may have a
cross-platform option, but it's not clear.
For now, just omit this functionality on Windows. If we want to expose
it, we can explore something in std::chrono or make a wrapper that
calls QueryPerformanceCounter on Windows.
* sigh
* fix bounds check
391 lines
No EOL
12 KiB
C
391 lines
No EOL
12 KiB
C
/*
|
|
* Copyright (c) 2012 David Siñuela Pastor, siu.4coders@gmail.com
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining
|
|
* a copy of this software and associated documentation files (the
|
|
* "Software"), to deal in the Software without restriction, including
|
|
* without limitation the rights to use, copy, modify, merge, publish,
|
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
|
* permit persons to whom the Software is furnished to do so, subject to
|
|
* the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be
|
|
* included in all copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
* LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
* OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
* WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
#ifndef MINUNIT_MINUNIT_H
|
|
#define MINUNIT_MINUNIT_H
|
|
|
|
#ifdef __cplusplus
|
|
extern "C" {
|
|
#endif
|
|
|
|
#if defined(_WIN32)
|
|
#include <Windows.h>
|
|
#if defined(_MSC_VER) && _MSC_VER < 1900
|
|
#define snprintf _snprintf
|
|
#define __func__ __FUNCTION__
|
|
#endif
|
|
|
|
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
|
|
|
/* Change POSIX C SOURCE version for pure c99 compilers */
|
|
#if !defined(_POSIX_C_SOURCE) || _POSIX_C_SOURCE < 200112L
|
|
#undef _POSIX_C_SOURCE
|
|
#define _POSIX_C_SOURCE 200112L
|
|
#endif
|
|
|
|
#include <unistd.h> /* POSIX flags */
|
|
#include <time.h> /* clock_gettime(), time() */
|
|
#include <sys/time.h> /* gethrtime(), gettimeofday() */
|
|
#include <sys/resource.h>
|
|
#include <sys/times.h>
|
|
#include <string.h>
|
|
|
|
#if defined(__MACH__) && defined(__APPLE__)
|
|
#include <mach/mach.h>
|
|
#include <mach/mach_time.h>
|
|
#endif
|
|
|
|
#if __GNUC__ >= 5 && !defined(__STDC_VERSION__)
|
|
#define __func__ __extension__ __FUNCTION__
|
|
#endif
|
|
|
|
#else
|
|
#error "Unable to define timers for an unknown OS."
|
|
#endif
|
|
|
|
#include <stdio.h>
|
|
#include <math.h>
|
|
|
|
/* Maximum length of last message */
|
|
#define MINUNIT_MESSAGE_LEN 1024
|
|
/* Accuracy with which floats are compared */
|
|
#define MINUNIT_EPSILON 1E-12
|
|
|
|
/* Misc. counters */
|
|
static int minunit_run = 0;
|
|
static int minunit_assert = 0;
|
|
static int minunit_fail = 0;
|
|
static int minunit_status = 0;
|
|
|
|
/* Timers */
|
|
static double minunit_real_timer = 0;
|
|
static double minunit_proc_timer = 0;
|
|
|
|
/* Last message */
|
|
static char minunit_last_message[MINUNIT_MESSAGE_LEN];
|
|
|
|
/* Test setup and teardown function pointers */
|
|
static void (*minunit_setup)(void) = NULL;
|
|
static void (*minunit_teardown)(void) = NULL;
|
|
|
|
/* Definitions */
|
|
#define MU_TEST(method_name) static void method_name(void)
|
|
#define MU_TEST_SUITE(suite_name) static void suite_name(void)
|
|
|
|
#define MU__SAFE_BLOCK(block) do {\
|
|
block\
|
|
} while(0)
|
|
|
|
/* Run test suite and unset setup and teardown functions */
|
|
#define MU_RUN_SUITE(suite_name) MU__SAFE_BLOCK(\
|
|
suite_name();\
|
|
minunit_setup = NULL;\
|
|
minunit_teardown = NULL;\
|
|
)
|
|
|
|
/* Configure setup and teardown functions */
|
|
#define MU_SUITE_CONFIGURE(setup_fun, teardown_fun) MU__SAFE_BLOCK(\
|
|
minunit_setup = setup_fun;\
|
|
minunit_teardown = teardown_fun;\
|
|
)
|
|
|
|
/* Test runner */
|
|
#define MU_RUN_TEST(test) MU__SAFE_BLOCK(\
|
|
if (minunit_real_timer==0 && minunit_proc_timer==0) {\
|
|
minunit_real_timer = mu_timer_real();\
|
|
minunit_proc_timer = mu_timer_cpu();\
|
|
}\
|
|
if (minunit_setup) (*minunit_setup)();\
|
|
minunit_status = 0;\
|
|
test();\
|
|
minunit_run++;\
|
|
if (minunit_status) {\
|
|
minunit_fail++;\
|
|
printf("F");\
|
|
printf("\n%s\n", minunit_last_message);\
|
|
}\
|
|
fflush(stdout);\
|
|
if (minunit_teardown) (*minunit_teardown)();\
|
|
)
|
|
|
|
/* Report */
|
|
#define MU_REPORT() MU__SAFE_BLOCK(\
|
|
double minunit_end_real_timer;\
|
|
double minunit_end_proc_timer;\
|
|
printf("\n\n%d tests, %d assertions, %d failures\n", minunit_run, minunit_assert, minunit_fail);\
|
|
minunit_end_real_timer = mu_timer_real();\
|
|
minunit_end_proc_timer = mu_timer_cpu();\
|
|
printf("\nFinished in %.8f seconds (real) %.8f seconds (proc)\n\n",\
|
|
minunit_end_real_timer - minunit_real_timer,\
|
|
minunit_end_proc_timer - minunit_proc_timer);\
|
|
)
|
|
#define MU_EXIT_CODE minunit_fail
|
|
|
|
/* Assertions */
|
|
#define mu_check(test) MU__SAFE_BLOCK(\
|
|
minunit_assert++;\
|
|
if (!(test)) {\
|
|
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, #test);\
|
|
minunit_status = 1;\
|
|
return;\
|
|
} else {\
|
|
printf(".");\
|
|
}\
|
|
)
|
|
|
|
#define mu_fail(message) MU__SAFE_BLOCK(\
|
|
minunit_assert++;\
|
|
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
|
|
minunit_status = 1;\
|
|
return;\
|
|
)
|
|
|
|
#define mu_assert(test, message) MU__SAFE_BLOCK(\
|
|
minunit_assert++;\
|
|
if (!(test)) {\
|
|
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %s", __func__, __FILE__, __LINE__, message);\
|
|
minunit_status = 1;\
|
|
return;\
|
|
} else {\
|
|
printf(".");\
|
|
}\
|
|
)
|
|
|
|
#define mu_assert_int_eq(expected, result) MU__SAFE_BLOCK(\
|
|
int minunit_tmp_e;\
|
|
int minunit_tmp_r;\
|
|
minunit_assert++;\
|
|
minunit_tmp_e = (expected);\
|
|
minunit_tmp_r = (result);\
|
|
if (minunit_tmp_e != minunit_tmp_r) {\
|
|
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %d expected but was %d", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
|
|
minunit_status = 1;\
|
|
return;\
|
|
} else {\
|
|
printf(".");\
|
|
}\
|
|
)
|
|
|
|
#define mu_assert_double_eq(expected, result) MU__SAFE_BLOCK(\
|
|
double minunit_tmp_e;\
|
|
double minunit_tmp_r;\
|
|
minunit_assert++;\
|
|
minunit_tmp_e = (expected);\
|
|
minunit_tmp_r = (result);\
|
|
if (fabs(minunit_tmp_e-minunit_tmp_r) > MINUNIT_EPSILON) {\
|
|
int minunit_significant_figures = 1 - log10(MINUNIT_EPSILON);\
|
|
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: %.*g expected but was %.*g", __func__, __FILE__, __LINE__, minunit_significant_figures, minunit_tmp_e, minunit_significant_figures, minunit_tmp_r);\
|
|
minunit_status = 1;\
|
|
return;\
|
|
} else {\
|
|
printf(".");\
|
|
}\
|
|
)
|
|
|
|
#define mu_assert_string_eq(expected, result) MU__SAFE_BLOCK(\
|
|
const char* minunit_tmp_e = expected;\
|
|
const char* minunit_tmp_r = result;\
|
|
minunit_assert++;\
|
|
if (!minunit_tmp_e) {\
|
|
minunit_tmp_e = "<null pointer>";\
|
|
}\
|
|
if (!minunit_tmp_r) {\
|
|
minunit_tmp_r = "<null pointer>";\
|
|
}\
|
|
if(strcmp(minunit_tmp_e, minunit_tmp_r)) {\
|
|
snprintf(minunit_last_message, MINUNIT_MESSAGE_LEN, "%s failed:\n\t%s:%d: '%s' expected but was '%s'", __func__, __FILE__, __LINE__, minunit_tmp_e, minunit_tmp_r);\
|
|
minunit_status = 1;\
|
|
return;\
|
|
} else {\
|
|
printf(".");\
|
|
}\
|
|
)
|
|
|
|
/*
|
|
* The following two functions were written by David Robert Nadeau
|
|
* from http://NadeauSoftware.com/ and distributed under the
|
|
* Creative Commons Attribution 3.0 Unported License
|
|
*/
|
|
|
|
/**
|
|
* Returns the real time, in seconds, or -1.0 if an error occurred.
|
|
*
|
|
* Time is measured since an arbitrary and OS-dependent start time.
|
|
* The returned real time is only useful for computing an elapsed time
|
|
* between two calls to this function.
|
|
*/
|
|
static double mu_timer_real(void)
|
|
{
|
|
#if defined(_WIN32)
|
|
/* Windows 2000 and later. ---------------------------------- */
|
|
LARGE_INTEGER Time;
|
|
LARGE_INTEGER Frequency;
|
|
|
|
QueryPerformanceFrequency(&Frequency);
|
|
QueryPerformanceCounter(&Time);
|
|
|
|
Time.QuadPart *= 1000000;
|
|
Time.QuadPart /= Frequency.QuadPart;
|
|
|
|
return (double)Time.QuadPart / 1000000.0;
|
|
|
|
#elif (defined(__hpux) || defined(hpux)) || ((defined(__sun__) || defined(__sun) || defined(sun)) && (defined(__SVR4) || defined(__svr4__)))
|
|
/* HP-UX, Solaris. ------------------------------------------ */
|
|
return (double)gethrtime( ) / 1000000000.0;
|
|
|
|
#elif defined(__MACH__) && defined(__APPLE__)
|
|
/* OSX. ----------------------------------------------------- */
|
|
static double timeConvert = 0.0;
|
|
if ( timeConvert == 0.0 )
|
|
{
|
|
mach_timebase_info_data_t timeBase;
|
|
(void)mach_timebase_info( &timeBase );
|
|
timeConvert = (double)timeBase.numer /
|
|
(double)timeBase.denom /
|
|
1000000000.0;
|
|
}
|
|
return (double)mach_absolute_time( ) * timeConvert;
|
|
|
|
#elif defined(_POSIX_VERSION)
|
|
/* POSIX. --------------------------------------------------- */
|
|
struct timeval tm;
|
|
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
|
|
{
|
|
struct timespec ts;
|
|
#if defined(CLOCK_MONOTONIC_PRECISE)
|
|
/* BSD. --------------------------------------------- */
|
|
const clockid_t id = CLOCK_MONOTONIC_PRECISE;
|
|
#elif defined(CLOCK_MONOTONIC_RAW)
|
|
/* Linux. ------------------------------------------- */
|
|
const clockid_t id = CLOCK_MONOTONIC_RAW;
|
|
#elif defined(CLOCK_HIGHRES)
|
|
/* Solaris. ----------------------------------------- */
|
|
const clockid_t id = CLOCK_HIGHRES;
|
|
#elif defined(CLOCK_MONOTONIC)
|
|
/* AIX, BSD, Linux, POSIX, Solaris. ----------------- */
|
|
const clockid_t id = CLOCK_MONOTONIC;
|
|
#elif defined(CLOCK_REALTIME)
|
|
/* AIX, BSD, HP-UX, Linux, POSIX. ------------------- */
|
|
const clockid_t id = CLOCK_REALTIME;
|
|
#else
|
|
const clockid_t id = (clockid_t)-1; /* Unknown. */
|
|
#endif /* CLOCK_* */
|
|
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
|
|
return (double)ts.tv_sec +
|
|
(double)ts.tv_nsec / 1000000000.0;
|
|
/* Fall thru. */
|
|
}
|
|
#endif /* _POSIX_TIMERS */
|
|
|
|
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, POSIX, Solaris. ----- */
|
|
gettimeofday( &tm, NULL );
|
|
return (double)tm.tv_sec + (double)tm.tv_usec / 1000000.0;
|
|
#else
|
|
return -1.0; /* Failed. */
|
|
#endif
|
|
}
|
|
|
|
/**
|
|
* Returns the amount of CPU time used by the current process,
|
|
* in seconds, or -1.0 if an error occurred.
|
|
*/
|
|
static double mu_timer_cpu(void)
|
|
{
|
|
#if defined(_WIN32)
|
|
/* Windows -------------------------------------------------- */
|
|
FILETIME createTime;
|
|
FILETIME exitTime;
|
|
FILETIME kernelTime;
|
|
FILETIME userTime;
|
|
|
|
/* This approach has a resolution of 1/64 second. Unfortunately, Windows' API does not offer better */
|
|
if ( GetProcessTimes( GetCurrentProcess( ),
|
|
&createTime, &exitTime, &kernelTime, &userTime ) != 0 )
|
|
{
|
|
ULARGE_INTEGER userSystemTime;
|
|
memcpy(&userSystemTime, &userTime, sizeof(ULARGE_INTEGER));
|
|
return (double)userSystemTime.QuadPart / 10000000.0;
|
|
}
|
|
|
|
#elif defined(__unix__) || defined(__unix) || defined(unix) || (defined(__APPLE__) && defined(__MACH__))
|
|
/* AIX, BSD, Cygwin, HP-UX, Linux, OSX, and Solaris --------- */
|
|
|
|
#if defined(_POSIX_TIMERS) && (_POSIX_TIMERS > 0)
|
|
/* Prefer high-res POSIX timers, when available. */
|
|
{
|
|
clockid_t id;
|
|
struct timespec ts;
|
|
#if _POSIX_CPUTIME > 0
|
|
/* Clock ids vary by OS. Query the id, if possible. */
|
|
if ( clock_getcpuclockid( 0, &id ) == -1 )
|
|
#endif
|
|
#if defined(CLOCK_PROCESS_CPUTIME_ID)
|
|
/* Use known clock id for AIX, Linux, or Solaris. */
|
|
id = CLOCK_PROCESS_CPUTIME_ID;
|
|
#elif defined(CLOCK_VIRTUAL)
|
|
/* Use known clock id for BSD or HP-UX. */
|
|
id = CLOCK_VIRTUAL;
|
|
#else
|
|
id = (clockid_t)-1;
|
|
#endif
|
|
if ( id != (clockid_t)-1 && clock_gettime( id, &ts ) != -1 )
|
|
return (double)ts.tv_sec +
|
|
(double)ts.tv_nsec / 1000000000.0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(RUSAGE_SELF)
|
|
{
|
|
struct rusage rusage;
|
|
if ( getrusage( RUSAGE_SELF, &rusage ) != -1 )
|
|
return (double)rusage.ru_utime.tv_sec +
|
|
(double)rusage.ru_utime.tv_usec / 1000000.0;
|
|
}
|
|
#endif
|
|
|
|
#if defined(_SC_CLK_TCK)
|
|
{
|
|
const double ticks = (double)sysconf( _SC_CLK_TCK );
|
|
struct tms tms;
|
|
if ( times( &tms ) != (clock_t)-1 )
|
|
return (double)tms.tms_utime / ticks;
|
|
}
|
|
#endif
|
|
|
|
#if defined(CLOCKS_PER_SEC)
|
|
{
|
|
clock_t cl = clock( );
|
|
if ( cl != (clock_t)-1 )
|
|
return (double)cl / (double)CLOCKS_PER_SEC;
|
|
}
|
|
#endif
|
|
|
|
#endif
|
|
|
|
return -1; /* Failed. */
|
|
}
|
|
|
|
#ifdef __cplusplus
|
|
}
|
|
#endif
|
|
|
|
#endif /* MINUNIT_MINUNIT_H */ |