Skip to content

Commit 9c0b5c9

Browse files
committed
During Histogram merge, fixed a possible issue with the bin edge logic during the import to HistogramVBW resulting from floating point errors in the bin edge calculation. Now the neighboring upper and lower bin edges in the VBW are forced to coincide.
Improved error checking and reporting for HistogramVBW. Fatal errors now output a stack trace to make bug hunting easier!
1 parent 80b8c4a commit 9c0b5c9

4 files changed

Lines changed: 171 additions & 9 deletions

File tree

include/chimbuko/util/Histogram.hpp

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -379,6 +379,17 @@ namespace chimbuko{
379379
static Bin* getBin(Bin* leftmost, double v);
380380
static std::pair<Bin*,Bin*> split(Bin* bin, double about);
381381
static size_t size(Bin* leftmost);
382+
/**
383+
* @brief Return bin information as a string
384+
*/
385+
static std::string getBinInfo(Bin* bin);
386+
static Bin* getChainStart(Bin* begin);
387+
/**
388+
* @brief Print the info output for the entire chain
389+
*
390+
* Provided bin can be any in the chain
391+
*/
392+
static std::string getChainInfo(Bin* any_bin);
382393
};
383394

384395
HistogramVBW(): first(nullptr), end(nullptr), m_min(0), m_max(0){}

include/chimbuko/util/error.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,12 @@ namespace chimbuko{
6969
*/
7070
void set_error_output_file(const int rank, const std::string &file_stub = "ad_error");
7171

72+
/**
73+
* @brief Print a stack trace
74+
*/
75+
void stacktrace(std::ostream &out, unsigned int max_frames = 63);
76+
77+
7278
/**
7379
* @brief Signal a recoverable error
7480
*/

src/util/Histogram.cpp

Lines changed: 66 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -642,9 +642,40 @@ namespace chimbuko{
642642
}
643643
}
644644

645+
std::string HistogramVBW::Bin::getBinInfo(Bin* bin){
646+
std::ostringstream os;
647+
if(!bin) os << "(null)";
648+
else os << *bin;
649+
return os.str();
650+
}
651+
652+
HistogramVBW::Bin* HistogramVBW::Bin::getChainStart(Bin* begin){
653+
if(!begin) fatal_error("Provided bin is null");
654+
Bin* f= begin;
655+
while(f->left != nullptr) f = f->left;
656+
return f;
657+
}
658+
659+
std::string HistogramVBW::Bin::getChainInfo(Bin* any_bin){
660+
std::ostringstream os;
661+
Bin* f = getChainStart(any_bin);
662+
while(!f->is_end){
663+
os << getBinInfo(f) << " ";
664+
f = f->right;
665+
}
666+
return os.str();
667+
}
668+
669+
645670
HistogramVBW::Bin* HistogramVBW::Bin::insertRight(HistogramVBW::Bin* of, HistogramVBW::Bin* toins){
646671
if(of == nullptr) fatal_error("Nullptr exception");
647672
if(of->is_end) fatal_error("Cannot insert right of end");
673+
if(toins->l < of->u || toins->u < of->u){
674+
std::ostringstream os; os << "Cannot insert " << getBinInfo(toins) << " right of " << getBinInfo(of) << " because the edges are not properly ordered: "
675+
<< toins->l << "<" << of->u << "?" << (toins->l < of->u) << " "
676+
<< toins->u << "<" << of->u << "?" << (toins->u < of->u) << std::endl;
677+
fatal_error(os.str());
678+
}
648679

649680
toins->right = of->right;
650681
toins->right->left = toins;
@@ -656,7 +687,8 @@ HistogramVBW::Bin* HistogramVBW::Bin::insertRight(HistogramVBW::Bin* of, Histogr
656687

657688
HistogramVBW::Bin* HistogramVBW::Bin::insertLeft(HistogramVBW::Bin* of, HistogramVBW::Bin* toins){
658689
if(of == nullptr) fatal_error("Nullptr exception");
659-
690+
if(!of->is_end && (toins->u > of->l || toins->l > of->l) ) fatal_error("Cannot insert " + getBinInfo(toins) + " left of " + getBinInfo(of) + " because the edges are not properly ordered");
691+
660692
toins->left = of->left;
661693
if(toins->left) toins->left->right = toins;
662694

@@ -703,7 +735,14 @@ HistogramVBW::Bin* HistogramVBW::Bin::getBin(HistogramVBW::Bin* start, double v)
703735
Bin* cur = start;
704736
Bin* next = cur->right;
705737
while(!cur->is_end){
706-
if(v <= cur->u) return cur;
738+
if(v <= cur->u){
739+
if(v <= cur->l){
740+
std::ostringstream os;
741+
os << "Logic error in bin search, expect " << v << " to be within bin " << getBinInfo(cur) << " but the value is below the lower edge. Possibly the chain edges are not ordered? Chain: " << getChainInfo(start);
742+
fatal_error(os.str());
743+
}
744+
return cur;
745+
}
707746
cur = next;
708747
next = cur->right;
709748
}
@@ -713,7 +752,14 @@ HistogramVBW::Bin* HistogramVBW::Bin::getBin(HistogramVBW::Bin* start, double v)
713752

714753
std::pair<HistogramVBW::Bin*,HistogramVBW::Bin*> HistogramVBW::Bin::split(HistogramVBW::Bin* bin, double about){
715754
if(bin == nullptr || bin->is_end) fatal_error("Invalid bin");
716-
if(about < bin->l || about > bin->u) fatal_error("Split location not inside bin");
755+
if(about <= bin->l || about > bin->u){
756+
std::ostringstream os;
757+
os << "Split location " << about << " not inside bin " << getBinInfo(bin) << "\n"
758+
<< "Tests for exclusion about < lower_edge: " << (about < bin->l) << " about > upper edge: " << (about > bin->u) << "\n"
759+
<< "Left neighbor bin " << getBinInfo(bin->left) << " and right neighbor bin " << getBinInfo(bin->right);
760+
fatal_error(os.str());
761+
}
762+
717763
verboseStream << "Splitting bin " << *bin << " about " << about << std::endl;
718764
if(about == bin->u){
719765
verboseStream << "Split point matches upper edge, doing nothing" << std::endl;
@@ -736,11 +782,12 @@ std::pair<HistogramVBW::Bin*,HistogramVBW::Bin*> HistogramVBW::Bin::split(Histog
736782

737783
//Assign debt to largest fraction with preference from left
738784
o[lrg] += debt;
739-
740-
insertRight(bin, new Bin(about, bin->u, o[1]));
741-
785+
double bu_prev = bin->u;
742786
bin->u = about;
743787
bin->c = o[0];
788+
789+
insertRight(bin, new Bin(about, bu_prev, o[1]));
790+
744791
verboseStream << "Split bin into " << *bin << " and " << *bin->right << std::endl;
745792
return {bin, bin->right};
746793
}
@@ -759,7 +806,7 @@ size_t HistogramVBW::Bin::size(HistogramVBW::Bin* first){
759806

760807
std::ostream & chimbuko::operator<<(std::ostream &os, const HistogramVBW::Bin &b){
761808
if(b.is_end) os << "(END)";
762-
else os << "(" << b.l << ":" << b.u << "; " << b.c << ")";
809+
else os << "(" << b.l << "," << b.u << "]{" << b.c << "}";
763810
return os;
764811
}
765812

@@ -811,10 +858,21 @@ void HistogramVBW::import(const Histogram &h){
811858
first = fe.first;
812859
end = fe.second;
813860

861+
//for this histogram we want to ensure the upper edge of the previous bin and the lower edge of the next
862+
//are identical. As the basic histogram computes these edges up to potential floating point errors, we enforce it manually here
863+
double prev_upper = be.second;
864+
814865
Bin* hp = first;
815866
for(int b=1;b<h.Nbin();b++){
816867
be = h.binEdges(b);
817-
hp = Bin::insertRight(hp, new Bin(be.first, be.second, h.binCount(b)) );
868+
double reldiff_l = 2*(be.first - prev_upper)/(be.first + prev_upper);
869+
if( fabs(reldiff_l) > 1e-8 ){
870+
std::ostringstream of; of << "Lower bin edge should match upper bin edge of previous bin! Got " << prev_upper << " " << be.first << " reldiff " << reldiff_l;
871+
fatal_error(of.str());
872+
}
873+
874+
hp = Bin::insertRight(hp, new Bin(prev_upper, be.second, h.binCount(b)) );
875+
prev_upper = be.second;
818876
}
819877
m_min = h.getMin();
820878
m_max = h.getMax();

src/util/error.cpp

Lines changed: 88 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@
88
#include <exception>
99
#include <memory>
1010

11+
#include <stdio.h>
12+
#include <stdlib.h>
13+
#include <execinfo.h>
14+
#include <cxxabi.h>
15+
1116
namespace chimbuko{
1217

1318
ErrorWriter::ErrorWriter(): m_rank(-1), m_ostream(&std::cerr){}
@@ -23,7 +28,7 @@ namespace chimbuko{
2328
}
2429
}
2530
void ErrorWriter::fatal(const std::string &msg, const std::string &func, const std::string &file, const unsigned long line){
26-
std::string err = getErrStr("FATAL", msg, func, file, line);
31+
std::string err = getErrStr("FATAL", msg, func, file, line);
2732
throw std::runtime_error(err);
2833
}
2934

@@ -33,6 +38,12 @@ namespace chimbuko{
3338
ss << '[' << getDateTime() << "] Error (" << type << ")";
3439
if(m_rank != -1) ss << " rank " << m_rank;
3540
ss << " : " << func << " (" << file << ":" << line << ") : " << msg <<std::endl;
41+
42+
if(type == "FATAL"){
43+
ss << "Stack trace:\n";
44+
stacktrace(ss);
45+
}
46+
3647
return ss.str();
3748
}
3849

@@ -90,4 +101,80 @@ namespace chimbuko{
90101
}
91102

92103

104+
//Based on stacktrace.h (c) 2008, Timo Bingmann from http://idlebox.net/ published under the WTFPL v2.0
105+
void stacktrace(std::ostream &out, unsigned int max_frames){
106+
// storage array for stack trace address data
107+
void* addrlist[max_frames+1];
108+
109+
// retrieve current stack addresses
110+
int addrlen = backtrace(addrlist, sizeof(addrlist) / sizeof(void*));
111+
112+
if (addrlen == 0) {
113+
out << "<empty, possibly corrupt>" << std::endl;
114+
return;
115+
}
116+
117+
// resolve addresses into strings containing "filename(function+address)",
118+
// this array must be free()-ed
119+
char** symbollist = backtrace_symbols(addrlist, addrlen);
120+
121+
// allocate string which will be filled with the demangled function name
122+
size_t funcnamesize = 256;
123+
char* funcname = (char*)malloc(funcnamesize);
124+
125+
// iterate over the returned symbol lines. skip the first, it is the
126+
// address of this function.
127+
for (int i = 0; i < addrlen; i++){
128+
char *begin_name = 0, *begin_offset = 0, *end_offset = 0;
129+
130+
// find parentheses and +address offset surrounding the mangled name:
131+
// ./module(function+0x15c) [0x8048a6d]
132+
for (char *p = symbollist[i]; *p; ++p)
133+
{
134+
if (*p == '(')
135+
begin_name = p;
136+
else if (*p == '+')
137+
begin_offset = p;
138+
else if (*p == ')' && begin_offset) {
139+
end_offset = p;
140+
break;
141+
}
142+
}
143+
144+
if (begin_name && begin_offset && end_offset
145+
&& begin_name < begin_offset)
146+
{
147+
*begin_name++ = '\0';
148+
*begin_offset++ = '\0';
149+
*end_offset = '\0';
150+
151+
// mangled name is now in [begin_name, begin_offset) and caller
152+
// offset in [begin_offset, end_offset). now apply
153+
// __cxa_demangle():
154+
155+
int status;
156+
char* ret = abi::__cxa_demangle(begin_name,
157+
funcname, &funcnamesize, &status);
158+
if (status == 0) {
159+
funcname = ret; // use possibly realloc()-ed string
160+
out << " " << symbollist[i] << " : " << funcname << "+" << begin_offset << std::endl;
161+
}
162+
else {
163+
// demangling failed. Output function name as a C function with
164+
// no arguments.
165+
out << " " << symbollist[i] << " : " << begin_name << "+" << begin_offset << std::endl;
166+
}
167+
}
168+
else
169+
{
170+
// couldn't parse the line? print the whole line.
171+
out << " " << symbollist[i] << std::endl;
172+
}
173+
}
174+
175+
free(funcname);
176+
free(symbollist);
177+
}
178+
179+
93180
}

0 commit comments

Comments
 (0)